Page Menu
Home
ClusterLabs Projects
Search
Configure Global Search
Log In
Files
F4639261
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
156 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/daemons/controld/controld_te_callbacks.c b/daemons/controld/controld_te_callbacks.c
index b74da0ffb5..c78ec37037 100644
--- a/daemons/controld/controld_te_callbacks.c
+++ b/daemons/controld/controld_te_callbacks.c
@@ -1,527 +1,527 @@
/*
* Copyright 2004-2025 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/stat.h>
#include <crm/crm.h>
#include <crm/common/xml.h>
#include <crm/common/xml_internal.h>
#include <libxml/xpath.h> // xmlXPathObject, etc.
#include <pacemaker-controld.h>
// An explicit PCMK_OPT_SHUTDOWN_LOCK of 0 means the lock has been cleared
static bool
shutdown_lock_cleared(xmlNode *lrm_resource)
{
time_t shutdown_lock = 0;
return (crm_element_value_epoch(lrm_resource, PCMK_OPT_SHUTDOWN_LOCK,
&shutdown_lock) == pcmk_ok)
&& (shutdown_lock == 0);
}
static void
process_lrm_resource_diff(xmlNode *lrm_resource, const char *node)
{
for (xmlNode *rsc_op = pcmk__xe_first_child(lrm_resource, NULL, NULL, NULL);
rsc_op != NULL; rsc_op = pcmk__xe_next(rsc_op, NULL)) {
process_graph_event(rsc_op, node);
}
if (shutdown_lock_cleared(lrm_resource)) {
// @TODO would be more efficient to abort once after transition done
abort_transition(PCMK_SCORE_INFINITY, pcmk__graph_restart,
"Shutdown lock cleared", lrm_resource);
}
}
static void
process_resource_updates(const char *node, xmlNode *xml, xmlNode *change,
const char *op, const char *xpath)
{
xmlNode *rsc = NULL;
if (xml == NULL) {
return;
}
if (pcmk__xe_is(xml, PCMK__XE_LRM)) {
xml = pcmk__xe_first_child(xml, PCMK__XE_LRM_RESOURCES, NULL, NULL);
CRM_CHECK(xml != NULL, return);
}
CRM_CHECK(pcmk__xe_is(xml, PCMK__XE_LRM_RESOURCES), return);
/*
* Updates by, or in response to, TE actions will never contain updates
* for more than one resource at a time, so such updates indicate an
* LRM refresh.
*
* In that case, start a new transition rather than check each result
* individually, which can result in _huge_ speedups in large clusters.
*
* Unfortunately, we can only do so when there are no pending actions.
* Otherwise, we could mistakenly throw away those results here, and
* the cluster will stall waiting for them and time out the operation.
*/
if ((controld_globals.transition_graph->pending == 0)
&& (xml->children != NULL) && (xml->children->next != NULL)) {
crm_log_xml_trace(change, "lrm-refresh");
abort_transition(PCMK_SCORE_INFINITY, pcmk__graph_restart,
"History refresh", NULL);
return;
}
for (rsc = pcmk__xe_first_child(xml, NULL, NULL, NULL); rsc != NULL;
rsc = pcmk__xe_next(rsc, NULL)) {
crm_trace("Processing %s", pcmk__xe_id(rsc));
process_lrm_resource_diff(rsc, node);
}
}
static char *extract_node_uuid(const char *xpath)
{
char *mutable_path = pcmk__str_copy(xpath);
char *node_uuid = NULL;
char *search = NULL;
char *match = NULL;
match = strstr(mutable_path, PCMK__XE_NODE_STATE "[@" PCMK_XA_ID "=\'");
if (match == NULL) {
free(mutable_path);
return NULL;
}
match += strlen(PCMK__XE_NODE_STATE "[@" PCMK_XA_ID "=\'");
search = strchr(match, '\'');
if (search == NULL) {
free(mutable_path);
return NULL;
}
search[0] = 0;
node_uuid = pcmk__str_copy(match);
free(mutable_path);
return node_uuid;
}
static void
abort_unless_down(const char *xpath, const char *op, xmlNode *change,
const char *reason)
{
char *node_uuid = NULL;
pcmk__graph_action_t *down = NULL;
if (!pcmk__str_eq(op, PCMK_VALUE_DELETE, pcmk__str_none)) {
abort_transition(PCMK_SCORE_INFINITY, pcmk__graph_restart, reason,
change);
return;
}
node_uuid = extract_node_uuid(xpath);
if(node_uuid == NULL) {
crm_err("Could not extract node ID from %s", xpath);
abort_transition(PCMK_SCORE_INFINITY, pcmk__graph_restart, reason,
change);
return;
}
down = match_down_event(node_uuid);
if (down == NULL) {
crm_trace("Not expecting %s to be down (%s)", node_uuid, xpath);
abort_transition(PCMK_SCORE_INFINITY, pcmk__graph_restart, reason,
change);
} else {
crm_trace("Expecting changes to %s (%s)", node_uuid, xpath);
}
free(node_uuid);
}
static void
process_op_deletion(const char *xpath, xmlNode *change)
{
char *mutable_key = pcmk__str_copy(xpath);
char *key;
char *node_uuid;
// Extract the part of xpath between last pair of single quotes
key = strrchr(mutable_key, '\'');
if (key != NULL) {
*key = '\0';
key = strrchr(mutable_key, '\'');
}
if (key == NULL) {
crm_warn("Ignoring malformed CIB update (resource deletion of %s)",
xpath);
free(mutable_key);
return;
}
++key;
node_uuid = extract_node_uuid(xpath);
if (confirm_cancel_action(key, node_uuid) == FALSE) {
abort_transition(PCMK_SCORE_INFINITY, pcmk__graph_restart,
"Resource operation removal", change);
}
free(mutable_key);
free(node_uuid);
}
static void
process_delete_diff(const char *xpath, const char *op, xmlNode *change)
{
if (strstr(xpath, "/" PCMK__XE_LRM_RSC_OP "[")) {
process_op_deletion(xpath, change);
} else if (strstr(xpath, "/" PCMK__XE_LRM "[")) {
abort_unless_down(xpath, op, change, "Resource state removal");
} else if (strstr(xpath, "/" PCMK__XE_NODE_STATE "[")) {
abort_unless_down(xpath, op, change, "Node state removal");
} else {
crm_trace("Ignoring delete of %s", xpath);
}
}
static void
process_node_state_diff(xmlNode *state, xmlNode *change, const char *op,
const char *xpath)
{
xmlNode *lrm = pcmk__xe_first_child(state, PCMK__XE_LRM, NULL, NULL);
process_resource_updates(pcmk__xe_id(state), lrm, change, op, xpath);
}
static void
process_status_diff(xmlNode *status, xmlNode *change, const char *op,
const char *xpath)
{
for (xmlNode *state = pcmk__xe_first_child(status, NULL, NULL, NULL);
state != NULL; state = pcmk__xe_next(state, NULL)) {
process_node_state_diff(state, change, op, xpath);
}
}
static void
process_cib_diff(xmlNode *cib, xmlNode *change, const char *op,
const char *xpath)
{
xmlNode *status = pcmk__xe_first_child(cib, PCMK_XE_STATUS, NULL, NULL);
xmlNode *config = pcmk__xe_first_child(cib, PCMK_XE_CONFIGURATION, NULL,
NULL);
if (status) {
process_status_diff(status, change, op, xpath);
}
if (config) {
abort_transition(PCMK_SCORE_INFINITY, pcmk__graph_restart,
"Non-status-only change", change);
}
}
static int
te_update_diff_element(xmlNode *change, void *userdata)
{
xmlNode *match = NULL;
const char *name = NULL;
const char *xpath = crm_element_value(change, PCMK_XA_PATH);
// Possible ops: create, modify, delete, move
const char *op = crm_element_value(change, PCMK_XA_OPERATION);
// Ignore uninteresting updates
if (op == NULL) {
return pcmk_rc_ok;
} else if (xpath == NULL) {
crm_trace("Ignoring %s change for version field", op);
return pcmk_rc_ok;
} else if ((strcmp(op, PCMK_VALUE_MOVE) == 0)
&& (strstr(xpath,
"/" PCMK_XE_CIB "/" PCMK_XE_CONFIGURATION
"/" PCMK_XE_RESOURCES) == NULL)) {
/* We still need to consider moves within the resources section,
* since they affect placement order.
*/
crm_trace("Ignoring move change at %s", xpath);
return pcmk_rc_ok;
}
// Find the result of create/modify ops
if (strcmp(op, PCMK_VALUE_CREATE) == 0) {
match = change->children;
} else if (strcmp(op, PCMK_VALUE_MODIFY) == 0) {
match = pcmk__xe_first_child(change, PCMK_XE_CHANGE_RESULT, NULL, NULL);
if(match) {
match = match->children;
}
} else if (!pcmk__str_any_of(op,
PCMK_VALUE_DELETE, PCMK_VALUE_MOVE,
NULL)) {
crm_warn("Ignoring malformed CIB update (%s operation on %s is unrecognized)",
op, xpath);
return pcmk_rc_ok;
}
if (match) {
if (match->type == XML_COMMENT_NODE) {
crm_trace("Ignoring %s operation for comment at %s", op, xpath);
return pcmk_rc_ok;
}
name = (const char *)match->name;
}
crm_trace("Handling %s operation for %s%s%s",
op, (xpath? xpath : "CIB"),
(name? " matched by " : ""), (name? name : ""));
if (strstr(xpath, "/" PCMK_XE_CIB "/" PCMK_XE_CONFIGURATION)) {
abort_transition(PCMK_SCORE_INFINITY, pcmk__graph_restart,
"Configuration change", change);
return pcmk_rc_cib_modified; // Won't be packaged with operation results we may be waiting for
} else if (strstr(xpath, "/" PCMK_XE_TICKETS)
|| pcmk__str_eq(name, PCMK_XE_TICKETS, pcmk__str_none)) {
abort_transition(PCMK_SCORE_INFINITY, pcmk__graph_restart,
"Ticket attribute change", change);
return pcmk_rc_cib_modified; // Won't be packaged with operation results we may be waiting for
} else if (strstr(xpath, "/" PCMK__XE_TRANSIENT_ATTRIBUTES "[")
|| pcmk__str_eq(name, PCMK__XE_TRANSIENT_ATTRIBUTES,
pcmk__str_none)) {
abort_unless_down(xpath, op, change, "Transient attribute change");
return pcmk_rc_cib_modified; // Won't be packaged with operation results we may be waiting for
} else if (strcmp(op, PCMK_VALUE_DELETE) == 0) {
process_delete_diff(xpath, op, change);
} else if (name == NULL) {
crm_warn("Ignoring malformed CIB update (%s at %s has no result)",
op, xpath);
} else if (strcmp(name, PCMK_XE_CIB) == 0) {
process_cib_diff(match, change, op, xpath);
} else if (strcmp(name, PCMK_XE_STATUS) == 0) {
process_status_diff(match, change, op, xpath);
} else if (strcmp(name, PCMK__XE_NODE_STATE) == 0) {
process_node_state_diff(match, change, op, xpath);
} else if (strcmp(name, PCMK__XE_LRM) == 0) {
process_resource_updates(pcmk__xe_id(match), match, change, op,
xpath);
} else if (strcmp(name, PCMK__XE_LRM_RESOURCES) == 0) {
char *local_node = pcmk__xpath_node_id(xpath, PCMK__XE_LRM);
process_resource_updates(local_node, match, change, op, xpath);
free(local_node);
} else if (strcmp(name, PCMK__XE_LRM_RESOURCE) == 0) {
char *local_node = pcmk__xpath_node_id(xpath, PCMK__XE_LRM);
process_lrm_resource_diff(match, local_node);
free(local_node);
} else if (strcmp(name, PCMK__XE_LRM_RSC_OP) == 0) {
char *local_node = pcmk__xpath_node_id(xpath, PCMK__XE_LRM);
process_graph_event(match, local_node);
free(local_node);
} else {
crm_warn("Ignoring malformed CIB update (%s at %s has unrecognized result %s)",
op, xpath, name);
}
return pcmk_rc_ok;
}
void
te_update_diff(const char *event, xmlNode * msg)
{
xmlNode *wrapper = NULL;
xmlNode *diff = NULL;
const char *op = NULL;
int rc = -EINVAL;
int format = 1;
int p_add[] = { 0, 0, 0 };
int p_del[] = { 0, 0, 0 };
CRM_CHECK(msg != NULL, return);
crm_element_value_int(msg, PCMK__XA_CIB_RC, &rc);
if (controld_globals.transition_graph == NULL) {
crm_trace("No graph");
return;
} else if (rc < pcmk_ok) {
crm_trace("Filter rc=%d (%s)", rc, pcmk_strerror(rc));
return;
} else if (controld_globals.transition_graph->complete
&& (controld_globals.fsa_state != S_IDLE)
&& (controld_globals.fsa_state != S_TRANSITION_ENGINE)
&& (controld_globals.fsa_state != S_POLICY_ENGINE)) {
crm_trace("Filter state=%s (complete)",
fsa_state2string(controld_globals.fsa_state));
return;
}
op = crm_element_value(msg, PCMK__XA_CIB_OP);
wrapper = pcmk__xe_first_child(msg, PCMK__XE_CIB_UPDATE_RESULT, NULL, NULL);
diff = pcmk__xe_first_child(wrapper, NULL, NULL, NULL);
- xml_patch_versions(diff, p_add, p_del);
+ pcmk__xml_patchset_versions(diff, p_del, p_add);
crm_debug("Processing (%s) diff: %d.%d.%d -> %d.%d.%d (%s)", op,
p_del[0], p_del[1], p_del[2], p_add[0], p_add[1], p_add[2],
fsa_state2string(controld_globals.fsa_state));
crm_element_value_int(diff, PCMK_XA_FORMAT, &format);
if (format == 2) {
crm_log_xml_trace(diff, "patch");
pcmk__xe_foreach_child(diff, NULL, te_update_diff_element, NULL);
} else {
crm_warn("Ignoring malformed CIB update (unknown patch format %d)",
format);
}
controld_remove_all_outside_events();
}
void
process_te_message(xmlNode * msg, xmlNode * xml_data)
{
const char *value = NULL;
xmlXPathObject *xpathObj = NULL;
int nmatches = 0;
CRM_CHECK(msg != NULL, return);
// Transition requests must specify transition engine as subsystem
value = crm_element_value(msg, PCMK__XA_CRM_SYS_TO);
if (pcmk__str_empty(value)
|| !pcmk__str_eq(value, CRM_SYSTEM_TENGINE, pcmk__str_none)) {
crm_info("Received invalid transition request: subsystem '%s' not '"
CRM_SYSTEM_TENGINE "'", pcmk__s(value, ""));
return;
}
// Only the lrm_invoke command is supported as a transition request
value = crm_element_value(msg, PCMK__XA_CRM_TASK);
if (!pcmk__str_eq(value, CRM_OP_INVOKE_LRM, pcmk__str_none)) {
crm_info("Received invalid transition request: command '%s' not '"
CRM_OP_INVOKE_LRM "'", pcmk__s(value, ""));
return;
}
// Transition requests must be marked as coming from the executor
value = crm_element_value(msg, PCMK__XA_CRM_SYS_FROM);
if (!pcmk__str_eq(value, CRM_SYSTEM_LRMD, pcmk__str_none)) {
crm_info("Received invalid transition request: from '%s' not '"
CRM_SYSTEM_LRMD "'", pcmk__s(value, ""));
return;
}
crm_debug("Processing transition request with ref='%s' origin='%s'",
pcmk__s(crm_element_value(msg, PCMK_XA_REFERENCE), ""),
pcmk__s(crm_element_value(msg, PCMK__XA_SRC), ""));
xpathObj = pcmk__xpath_search(xml_data->doc, "//" PCMK__XE_LRM_RSC_OP);
nmatches = pcmk__xpath_num_results(xpathObj);
if (nmatches == 0) {
crm_err("Received transition request with no results (bug?)");
} else {
for (int lpc = 0; lpc < nmatches; lpc++) {
xmlNode *rsc_op = pcmk__xpath_result(xpathObj, lpc);
if (rsc_op != NULL) {
const char *node = get_node_id(rsc_op);
process_graph_event(rsc_op, node);
}
}
}
xmlXPathFreeObject(xpathObj);
}
void
cib_action_updated(xmlNode * msg, int call_id, int rc, xmlNode * output, void *user_data)
{
if (rc < pcmk_ok) {
crm_err("Update %d FAILED: %s", call_id, pcmk_strerror(rc));
}
}
/*!
* \brief Handle a timeout in node-to-node communication
*
* \param[in,out] data Pointer to graph action
*
* \return FALSE (indicating that source should be not be re-added)
*/
gboolean
action_timer_callback(gpointer data)
{
pcmk__graph_action_t *action = (pcmk__graph_action_t *) data;
const char *task = NULL;
const char *on_node = NULL;
const char *via_node = NULL;
CRM_CHECK(data != NULL, return FALSE);
stop_te_timer(action);
task = crm_element_value(action->xml, PCMK_XA_OPERATION);
on_node = crm_element_value(action->xml, PCMK__META_ON_NODE);
via_node = crm_element_value(action->xml, PCMK__XA_ROUTER_NODE);
if (controld_globals.transition_graph->complete) {
crm_notice("Node %s did not send %s result (via %s) within %dms "
"(ignoring because transition not in progress)",
(on_node? on_node : ""), (task? task : "unknown action"),
(via_node? via_node : "controller"), action->timeout);
} else {
/* fail the action */
crm_err("Node %s did not send %s result (via %s) within %dms "
"(action timeout plus " PCMK_OPT_CLUSTER_DELAY ")",
(on_node? on_node : ""), (task? task : "unknown action"),
(via_node? via_node : "controller"),
(action->timeout
+ controld_globals.transition_graph->network_delay));
pcmk__log_graph_action(LOG_ERR, action);
pcmk__set_graph_action_flags(action, pcmk__graph_action_failed);
te_action_confirmed(action, controld_globals.transition_graph);
abort_transition(PCMK_SCORE_INFINITY, pcmk__graph_restart,
"Action lost", NULL);
// Record timeout in the CIB if appropriate
if ((action->type == pcmk__rsc_graph_action)
&& controld_action_is_recordable(task)) {
controld_record_action_timeout(action);
}
}
return FALSE;
}
diff --git a/daemons/controld/controld_te_utils.c b/daemons/controld/controld_te_utils.c
index 80d3c9477a..289aa21b46 100644
--- a/daemons/controld/controld_te_utils.c
+++ b/daemons/controld/controld_te_utils.c
@@ -1,508 +1,508 @@
/*
- * Copyright 2004-2024 the Pacemaker project contributors
+ * Copyright 2004-2025 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU General Public License version 2
* or later (GPLv2+) WITHOUT ANY WARRANTY.
*/
#include <crm_internal.h>
#include <crm/crm.h>
#include <crm/common/xml.h>
#include <pacemaker-controld.h>
//! Triggers transition graph processing
static crm_trigger_t *transition_trigger = NULL;
static GHashTable *node_pending_timers = NULL;
gboolean
stop_te_timer(pcmk__graph_action_t *action)
{
if (action == NULL) {
return FALSE;
}
if (action->timer != 0) {
crm_trace("Stopping action timer");
g_source_remove(action->timer);
action->timer = 0;
} else {
crm_trace("Action timer was already stopped");
return FALSE;
}
return TRUE;
}
static gboolean
te_graph_trigger(gpointer user_data)
{
if (controld_globals.transition_graph == NULL) {
crm_debug("Nothing to do");
return TRUE;
}
crm_trace("Invoking graph %d in state %s",
controld_globals.transition_graph->id,
fsa_state2string(controld_globals.fsa_state));
switch (controld_globals.fsa_state) {
case S_STARTING:
case S_PENDING:
case S_NOT_DC:
case S_HALT:
case S_ILLEGAL:
case S_STOPPING:
case S_TERMINATE:
return TRUE;
default:
break;
}
if (!controld_globals.transition_graph->complete) {
enum pcmk__graph_status graph_rc;
int orig_limit = controld_globals.transition_graph->batch_limit;
int throttled_limit = throttle_get_total_job_limit(orig_limit);
controld_globals.transition_graph->batch_limit = throttled_limit;
graph_rc = pcmk__execute_graph(controld_globals.transition_graph);
controld_globals.transition_graph->batch_limit = orig_limit;
if (graph_rc == pcmk__graph_active) {
crm_trace("Transition not yet complete");
return TRUE;
} else if (graph_rc == pcmk__graph_pending) {
crm_trace("Transition not yet complete - no actions fired");
return TRUE;
}
if (graph_rc != pcmk__graph_complete) {
crm_warn("Transition failed: %s",
pcmk__graph_status2text(graph_rc));
pcmk__log_graph(LOG_NOTICE, controld_globals.transition_graph);
}
}
crm_debug("Transition %d is now complete",
controld_globals.transition_graph->id);
controld_globals.transition_graph->complete = true;
notify_crmd(controld_globals.transition_graph);
return TRUE;
}
/*!
* \internal
* \brief Initialize transition trigger
*/
void
controld_init_transition_trigger(void)
{
transition_trigger = mainloop_add_trigger(G_PRIORITY_LOW, te_graph_trigger,
NULL);
}
/*!
* \internal
* \brief Destroy transition trigger
*/
void
controld_destroy_transition_trigger(void)
{
mainloop_destroy_trigger(transition_trigger);
transition_trigger = NULL;
}
void
controld_trigger_graph_as(const char *fn, int line)
{
crm_trace("%s:%d - Triggered graph processing", fn, line);
mainloop_set_trigger(transition_trigger);
}
static struct abort_timer_s {
bool aborted;
guint id;
int priority;
enum pcmk__graph_next action;
const char *text;
} abort_timer = { 0, };
static gboolean
abort_timer_popped(gpointer data)
{
struct abort_timer_s *abort_timer = (struct abort_timer_s *) data;
if (AM_I_DC && (abort_timer->aborted == FALSE)) {
abort_transition(abort_timer->priority, abort_timer->action,
abort_timer->text, NULL);
}
abort_timer->id = 0;
return FALSE; // do not immediately reschedule timer
}
/*!
* \internal
* \brief Abort transition after delay, if not already aborted in that time
*
* \param[in] abort_text Must be literal string
*/
void
abort_after_delay(int abort_priority, enum pcmk__graph_next abort_action,
const char *abort_text, guint delay_ms)
{
if (abort_timer.id) {
// Timer already in progress, stop and reschedule
g_source_remove(abort_timer.id);
}
abort_timer.aborted = FALSE;
abort_timer.priority = abort_priority;
abort_timer.action = abort_action;
abort_timer.text = abort_text;
abort_timer.id = pcmk__create_timer(delay_ms, abort_timer_popped, &abort_timer);
}
static void
free_node_pending_timer(gpointer data)
{
struct abort_timer_s *node_pending_timer = (struct abort_timer_s *) data;
if (node_pending_timer->id != 0) {
g_source_remove(node_pending_timer->id);
node_pending_timer->id = 0;
}
free(node_pending_timer);
}
static gboolean
node_pending_timer_popped(gpointer key)
{
struct abort_timer_s *node_pending_timer = NULL;
if (node_pending_timers == NULL) {
return FALSE;
}
node_pending_timer = g_hash_table_lookup(node_pending_timers, key);
if (node_pending_timer == NULL) {
return FALSE;
}
crm_warn("Node with " PCMK_XA_ID " '%s' pending timed out (%us) "
"on joining the process group",
(const char *) key, controld_globals.node_pending_timeout);
if (controld_globals.node_pending_timeout > 0) {
abort_timer_popped(node_pending_timer);
}
g_hash_table_remove(node_pending_timers, key);
return FALSE; // do not reschedule timer
}
static void
init_node_pending_timer(const pcmk__node_status_t *node, guint timeout)
{
struct abort_timer_s *node_pending_timer = NULL;
char *key = NULL;
if (node->xml_id == NULL) {
return;
}
if (node_pending_timers == NULL) {
node_pending_timers = pcmk__strikey_table(free,
free_node_pending_timer);
// The timer is somehow already existing
} else if (g_hash_table_lookup(node_pending_timers, node->xml_id) != NULL) {
return;
}
crm_notice("Waiting for pending %s with " PCMK_XA_ID " '%s' "
"to join the process group (timeout=%us)",
pcmk__s(node->name, "node"), node->xml_id,
controld_globals.node_pending_timeout);
key = pcmk__str_copy(node->xml_id);
node_pending_timer = pcmk__assert_alloc(1, sizeof(struct abort_timer_s));
node_pending_timer->aborted = FALSE;
node_pending_timer->priority = PCMK_SCORE_INFINITY;
node_pending_timer->action = pcmk__graph_restart;
node_pending_timer->text = "Node pending timed out";
g_hash_table_replace(node_pending_timers, key, node_pending_timer);
node_pending_timer->id = pcmk__create_timer(timeout * 1000,
node_pending_timer_popped,
key);
pcmk__assert(node_pending_timer->id != 0);
}
static void
remove_node_pending_timer(const char *node_uuid)
{
if (node_pending_timers == NULL) {
return;
}
g_hash_table_remove(node_pending_timers, node_uuid);
}
void
controld_node_pending_timer(const pcmk__node_status_t *node)
{
long long remaining_timeout = 0;
/* If the node is not an active cluster node, is leaving the cluster, or is
* already part of CPG, or PCMK_OPT_NODE_PENDING_TIMEOUT is disabled, free
* any node pending timer for it.
*/
if (pcmk_is_set(node->flags, pcmk__node_status_remote)
|| (node->when_member <= 1) || (node->when_online > 0)
|| (controld_globals.node_pending_timeout == 0)) {
remove_node_pending_timer(node->xml_id);
return;
}
// Node is a cluster member but offline in CPG
remaining_timeout = node->when_member - time(NULL)
+ controld_globals.node_pending_timeout;
/* It already passed node pending timeout somehow.
* Free any node pending timer of it.
*/
if (remaining_timeout <= 0) {
remove_node_pending_timer(node->xml_id);
return;
}
init_node_pending_timer(node, remaining_timeout);
}
void
controld_free_node_pending_timers(void)
{
if (node_pending_timers == NULL) {
return;
}
g_hash_table_destroy(node_pending_timers);
node_pending_timers = NULL;
}
static const char *
abort2text(enum pcmk__graph_next abort_action)
{
switch (abort_action) {
case pcmk__graph_done: return "done";
case pcmk__graph_wait: return "stop";
case pcmk__graph_restart: return "restart";
case pcmk__graph_shutdown: return "shutdown";
}
return "unknown";
}
static bool
update_abort_priority(pcmk__graph_t *graph, int priority,
enum pcmk__graph_next action, const char *abort_reason)
{
bool change = FALSE;
if (graph == NULL) {
return change;
}
if (graph->abort_priority < priority) {
crm_debug("Abort priority upgraded from %d to %d", graph->abort_priority, priority);
graph->abort_priority = priority;
if (graph->abort_reason != NULL) {
crm_debug("'%s' abort superseded by %s", graph->abort_reason, abort_reason);
}
graph->abort_reason = abort_reason;
change = TRUE;
}
if (graph->completion_action < action) {
crm_debug("Abort action %s superseded by %s: %s",
abort2text(graph->completion_action), abort2text(action), abort_reason);
graph->completion_action = action;
change = TRUE;
}
return change;
}
void
abort_transition_graph(int abort_priority, enum pcmk__graph_next abort_action,
const char *abort_text, const xmlNode *reason,
const char *fn, int line)
{
int add[] = { 0, 0, 0 };
int del[] = { 0, 0, 0 };
int level = LOG_INFO;
const xmlNode *diff = NULL;
const xmlNode *change = NULL;
CRM_CHECK(controld_globals.transition_graph != NULL, return);
switch (controld_globals.fsa_state) {
case S_STARTING:
case S_PENDING:
case S_NOT_DC:
case S_HALT:
case S_ILLEGAL:
case S_STOPPING:
case S_TERMINATE:
crm_info("Abort %s suppressed: state=%s (%scomplete)",
abort_text, fsa_state2string(controld_globals.fsa_state),
(controld_globals.transition_graph->complete? "" : "in"));
return;
default:
break;
}
abort_timer.aborted = TRUE;
controld_expect_sched_reply(NULL);
if (!controld_globals.transition_graph->complete
&& update_abort_priority(controld_globals.transition_graph,
abort_priority, abort_action,
abort_text)) {
level = LOG_NOTICE;
}
if (reason != NULL) {
const xmlNode *search = NULL;
for(search = reason; search; search = search->parent) {
if (pcmk__xe_is(search, PCMK_XE_DIFF)) {
diff = search;
break;
}
}
if(diff) {
- xml_patch_versions(diff, add, del);
+ pcmk__xml_patchset_versions(diff, del, add);
for(search = reason; search; search = search->parent) {
if (pcmk__xe_is(search, PCMK_XE_CHANGE)) {
change = search;
break;
}
}
}
}
if (reason == NULL) {
do_crm_log(level,
"Transition %d aborted: %s " QB_XS " source=%s:%d "
"complete=%s", controld_globals.transition_graph->id,
abort_text, fn, line,
pcmk__btoa(controld_globals.transition_graph->complete));
} else if(change == NULL) {
GString *local_path = pcmk__element_xpath(reason);
pcmk__assert(local_path != NULL);
do_crm_log(level, "Transition %d aborted by %s.%s: %s "
QB_XS " cib=%d.%d.%d source=%s:%d path=%s complete=%s",
controld_globals.transition_graph->id, reason->name,
pcmk__xe_id(reason), abort_text, add[0], add[1], add[2], fn,
line, (const char *) local_path->str,
pcmk__btoa(controld_globals.transition_graph->complete));
g_string_free(local_path, TRUE);
} else {
const char *op = crm_element_value(change, PCMK_XA_OPERATION);
const char *path = crm_element_value(change, PCMK_XA_PATH);
if(change == reason) {
if (strcmp(op, PCMK_VALUE_CREATE) == 0) {
reason = reason->children;
} else if (strcmp(op, PCMK_VALUE_MODIFY) == 0) {
reason = pcmk__xe_first_child(reason, PCMK_XE_CHANGE_RESULT,
NULL, NULL);
if(reason) {
reason = reason->children;
}
}
CRM_CHECK(reason != NULL, goto done);
}
if (strcmp(op, PCMK_VALUE_DELETE) == 0) {
const char *shortpath = strrchr(path, '/');
do_crm_log(level, "Transition %d aborted by deletion of %s: %s "
QB_XS " cib=%d.%d.%d source=%s:%d path=%s complete=%s",
controld_globals.transition_graph->id,
(shortpath? (shortpath + 1) : path), abort_text,
add[0], add[1], add[2], fn, line, path,
pcmk__btoa(controld_globals.transition_graph->complete));
} else if (pcmk__xe_is(reason, PCMK_XE_NVPAIR)) {
do_crm_log(level, "Transition %d aborted by %s doing %s %s=%s: %s "
QB_XS " cib=%d.%d.%d source=%s:%d path=%s complete=%s",
controld_globals.transition_graph->id,
crm_element_value(reason, PCMK_XA_ID), op,
crm_element_value(reason, PCMK_XA_NAME),
crm_element_value(reason, PCMK_XA_VALUE),
abort_text, add[0], add[1], add[2], fn, line, path,
pcmk__btoa(controld_globals.transition_graph->complete));
} else if (pcmk__xe_is(reason, PCMK__XE_LRM_RSC_OP)) {
const char *magic = crm_element_value(reason,
PCMK__XA_TRANSITION_MAGIC);
do_crm_log(level, "Transition %d aborted by operation %s '%s' on %s: %s "
QB_XS " magic=%s cib=%d.%d.%d source=%s:%d complete=%s",
controld_globals.transition_graph->id,
crm_element_value(reason, PCMK__XA_OPERATION_KEY), op,
crm_element_value(reason, PCMK__META_ON_NODE),
abort_text,
magic, add[0], add[1], add[2], fn, line,
pcmk__btoa(controld_globals.transition_graph->complete));
} else if (pcmk__str_any_of((const char *) reason->name,
PCMK__XE_NODE_STATE, PCMK_XE_NODE, NULL)) {
const char *uname = pcmk__node_name_from_uuid(pcmk__xe_id(reason));
do_crm_log(level, "Transition %d aborted by %s '%s' on %s: %s "
QB_XS " cib=%d.%d.%d source=%s:%d complete=%s",
controld_globals.transition_graph->id,
reason->name, op, pcmk__s(uname, pcmk__xe_id(reason)),
abort_text, add[0], add[1], add[2], fn, line,
pcmk__btoa(controld_globals.transition_graph->complete));
} else {
const char *id = pcmk__xe_id(reason);
do_crm_log(level, "Transition %d aborted by %s.%s '%s': %s "
QB_XS " cib=%d.%d.%d source=%s:%d path=%s complete=%s",
controld_globals.transition_graph->id,
reason->name, pcmk__s(id, ""), pcmk__s(op, "change"),
abort_text, add[0], add[1], add[2], fn, line, path,
pcmk__btoa(controld_globals.transition_graph->complete));
}
}
done:
if (controld_globals.transition_graph->complete) {
if (controld_get_period_transition_timer() > 0) {
controld_stop_transition_timer();
controld_start_transition_timer();
} else {
register_fsa_input(C_FSA_INTERNAL, I_PE_CALC, NULL);
}
return;
}
trigger_graph();
}
diff --git a/daemons/fenced/fenced_cib.c b/daemons/fenced/fenced_cib.c
index e76b25aeef..9fdd7d9e35 100644
--- a/daemons/fenced/fenced_cib.c
+++ b/daemons/fenced/fenced_cib.c
@@ -1,636 +1,636 @@
/*
* Copyright 2009-2025 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU General Public License version 2
* or later (GPLv2+) WITHOUT ANY WARRANTY.
*/
#include <crm_internal.h>
#include <stdbool.h>
#include <stdio.h>
#include <libxml/tree.h> // xmlNode
#include <libxml/xpath.h> // xmlXPathObject, etc.
#include <crm/crm.h>
#include <crm/common/xml.h>
#include <crm/cluster/internal.h>
#include <crm/cib.h>
#include <crm/cib/internal.h>
#include <pacemaker-fenced.h>
static xmlNode *local_cib = NULL;
static cib_t *cib_api = NULL;
static bool have_cib_devices = FALSE;
/*!
* \internal
* \brief Check whether a node has a specific attribute name/value
*
* \param[in] node Name of node to check
* \param[in] name Name of an attribute to look for
* \param[in] value The value the named attribute needs to be set to in order to be considered a match
*
* \return TRUE if the locally cached CIB has the specified node attribute
*/
gboolean
node_has_attr(const char *node, const char *name, const char *value)
{
GString *xpath = NULL;
xmlNode *match;
CRM_CHECK((local_cib != NULL) && (node != NULL) && (name != NULL)
&& (value != NULL), return FALSE);
/* Search for the node's attributes in the CIB. While the schema allows
* multiple sets of instance attributes, and allows instance attributes to
* use id-ref to reference values elsewhere, that is intended for resources,
* so we ignore that here.
*/
xpath = g_string_sized_new(256);
pcmk__g_strcat(xpath,
"//" PCMK_XE_NODES "/" PCMK_XE_NODE
"[@" PCMK_XA_UNAME "='", node, "']"
"/" PCMK_XE_INSTANCE_ATTRIBUTES
"/" PCMK_XE_NVPAIR
"[@" PCMK_XA_NAME "='", name, "' "
"and @" PCMK_XA_VALUE "='", value, "']", NULL);
match = pcmk__xpath_find_one(local_cib->doc, xpath->str, LOG_NEVER);
g_string_free(xpath, TRUE);
return (match != NULL);
}
static void
remove_topology_level(xmlNode *match)
{
int index = 0;
char *key = NULL;
xmlNode *data = NULL;
CRM_CHECK(match != NULL, return);
key = stonith_level_key(match, fenced_target_by_unknown);
crm_element_value_int(match, PCMK_XA_INDEX, &index);
data = pcmk__xe_create(NULL, PCMK_XE_FENCING_LEVEL);
crm_xml_add(data, PCMK__XA_ST_ORIGIN, __func__);
crm_xml_add(data, PCMK_XA_TARGET, key);
crm_xml_add_int(data, PCMK_XA_INDEX, index);
fenced_unregister_level(data, NULL);
free(key);
pcmk__xml_free(data);
}
static void
register_fencing_topology(xmlXPathObjectPtr xpathObj)
{
int max = pcmk__xpath_num_results(xpathObj);
for (int lpc = 0; lpc < max; lpc++) {
xmlNode *match = pcmk__xpath_result(xpathObj, lpc);
if (match == NULL) {
continue;
}
remove_topology_level(match);
fenced_register_level(match, NULL);
}
}
/* Fencing
<diff crm_feature_set="3.0.6">
<diff-removed>
<fencing-topology>
<fencing-level id="f-p1.1" target="pcmk-1" index="1" devices="poison-pill" __crm_diff_marker__="removed:top"/>
<fencing-level id="f-p1.2" target="pcmk-1" index="2" devices="power" __crm_diff_marker__="removed:top"/>
<fencing-level devices="disk,network" id="f-p2.1"/>
</fencing-topology>
</diff-removed>
<diff-added>
<fencing-topology>
<fencing-level id="f-p.1" target="pcmk-1" index="1" devices="poison-pill" __crm_diff_marker__="added:top"/>
<fencing-level id="f-p2.1" target="pcmk-2" index="1" devices="disk,something"/>
<fencing-level id="f-p3.1" target="pcmk-2" index="2" devices="power" __crm_diff_marker__="added:top"/>
</fencing-topology>
</diff-added>
</diff>
*/
void
fencing_topology_init(void)
{
xmlXPathObject *xpathObj = NULL;
const char *xpath = "//" PCMK_XE_FENCING_LEVEL;
crm_trace("Full topology refresh");
free_topology_list();
init_topology_list();
/* Grab everything */
xpathObj = pcmk__xpath_search(local_cib->doc, xpath);
register_fencing_topology(xpathObj);
xmlXPathFreeObject(xpathObj);
}
#define XPATH_WATCHDOG_TIMEOUT "//" PCMK_XE_NVPAIR \
"[@" PCMK_XA_NAME "='" \
PCMK_OPT_STONITH_WATCHDOG_TIMEOUT "']"
static void
update_stonith_watchdog_timeout_ms(xmlNode *cib)
{
long long timeout_ms = 0;
xmlNode *stonith_watchdog_xml = NULL;
const char *value = NULL;
// @TODO An XPath search can't handle multiple instances or rules
stonith_watchdog_xml = pcmk__xpath_find_one(cib->doc,
XPATH_WATCHDOG_TIMEOUT,
LOG_NEVER);
if (stonith_watchdog_xml) {
value = crm_element_value(stonith_watchdog_xml, PCMK_XA_VALUE);
}
if (value) {
timeout_ms = crm_get_msec(value);
}
if (timeout_ms < 0) {
timeout_ms = pcmk__auto_stonith_watchdog_timeout();
}
stonith_watchdog_timeout_ms = timeout_ms;
}
/*!
* \internal
* \brief Mark a fence device dirty if its \c fenced_df_cib_registered flag is
* set
*
* \param[in] key Ignored
* \param[in,out] value Fence device (<tt>fenced_device_t *</tt>)
* \param[in] user_data Ignored
*
* \note This function is suitable for use with \c g_hash_table_foreach().
*/
static void
mark_dirty_if_cib_registered(gpointer key, gpointer value, gpointer user_data)
{
fenced_device_t *device = value;
if (pcmk_is_set(device->flags, fenced_df_cib_registered)) {
fenced_device_set_flags(device, fenced_df_dirty);
}
}
/*!
* \internal
* \brief Return the value of a fence device's \c dirty flag
*
* \param[in] key Ignored
* \param[in] value Fence device (<tt>fenced_device_t *</tt>)
* \param[in] user_data Ignored
*
* \return \c dirty flag of \p value
*
* \note This function is suitable for use with
* \c g_hash_table_foreach_remove().
*/
static gboolean
device_is_dirty(gpointer key, gpointer value, gpointer user_data)
{
fenced_device_t *device = value;
return pcmk_is_set(device->flags, fenced_df_dirty);
}
/*!
* \internal
* \brief Update all STONITH device definitions based on current CIB
*/
static void
cib_devices_update(void)
{
crm_info("Updating devices to version %s.%s.%s",
crm_element_value(local_cib, PCMK_XA_ADMIN_EPOCH),
crm_element_value(local_cib, PCMK_XA_EPOCH),
crm_element_value(local_cib, PCMK_XA_NUM_UPDATES));
fenced_foreach_device(mark_dirty_if_cib_registered, NULL);
/* have list repopulated if cib has a watchdog-fencing-resource
TODO: keep a cached list for queries happening while we are refreshing
*/
g_list_free_full(stonith_watchdog_targets, free);
stonith_watchdog_targets = NULL;
fenced_scheduler_run(local_cib);
fenced_foreach_device_remove(device_is_dirty);
}
#define PRIMITIVE_ID_XP_FRAGMENT "/" PCMK_XE_PRIMITIVE "[@" PCMK_XA_ID "='"
static void
update_cib_stonith_devices(const xmlNode *patchset)
{
char *reason = NULL;
for (const xmlNode *change = pcmk__xe_first_child(patchset, NULL, NULL,
NULL);
change != NULL; change = pcmk__xe_next(change, NULL)) {
const char *op = crm_element_value(change, PCMK_XA_OPERATION);
const char *xpath = crm_element_value(change, PCMK_XA_PATH);
const char *primitive_xpath = NULL;
if (pcmk__str_eq(op, PCMK_VALUE_MOVE, pcmk__str_null_matches)
|| (strstr(xpath, "/" PCMK_XE_STATUS) != NULL)) {
continue;
}
primitive_xpath = strstr(xpath, PRIMITIVE_ID_XP_FRAGMENT);
if ((primitive_xpath != NULL)
&& pcmk__str_eq(op, PCMK_VALUE_DELETE, pcmk__str_none)) {
const char *rsc_id = NULL;
const char *end_quote = NULL;
if ((strstr(primitive_xpath, PCMK_XE_INSTANCE_ATTRIBUTES) != NULL)
|| (strstr(primitive_xpath, PCMK_XE_META_ATTRIBUTES) != NULL)) {
reason = pcmk__str_copy("(meta) attribute deleted from "
"resource");
break;
}
rsc_id = primitive_xpath + sizeof(PRIMITIVE_ID_XP_FRAGMENT) - 1;
end_quote = strchr(rsc_id, '\'');
CRM_LOG_ASSERT(end_quote != NULL);
if (end_quote == NULL) {
crm_err("Bug: Malformed item in Pacemaker-generated patchset");
continue;
}
if (strchr(end_quote, '/') == NULL) {
/* The primitive element itself was deleted. If this was a
* fencing resource, it's faster to remove it directly than to
* run the scheduler and update all device registrations.
*/
char *copy = strndup(rsc_id, end_quote - rsc_id);
pcmk__assert(copy != NULL);
stonith_device_remove(copy, true);
/* watchdog_device_update called afterwards
to fall back to implicit definition if needed */
free(copy);
continue;
}
}
if (strstr(xpath, "/" PCMK_XE_RESOURCES)
|| strstr(xpath, "/" PCMK_XE_CONSTRAINTS)
|| strstr(xpath, "/" PCMK_XE_RSC_DEFAULTS)) {
const char *shortpath = strrchr(xpath, '/');
reason = crm_strdup_printf("%s %s", op, shortpath + 1);
break;
}
}
if (reason != NULL) {
crm_info("Updating device list from CIB: %s", reason);
cib_devices_update();
free(reason);
} else {
crm_trace("No updates for device list found in CIB");
}
}
static void
watchdog_device_update(void)
{
if (stonith_watchdog_timeout_ms > 0) {
if (!fenced_has_watchdog_device()
&& (stonith_watchdog_targets == NULL)) {
/* getting here watchdog-fencing enabled, no device there yet
and reason isn't stonith_watchdog_targets preventing that
*/
int rc;
xmlNode *xml;
xml = create_device_registration_xml(
STONITH_WATCHDOG_ID,
st_namespace_internal,
STONITH_WATCHDOG_AGENT,
NULL, /* fenced_device_register() will add our
own name as PCMK_STONITH_HOST_LIST param
so we can skip that here
*/
NULL);
rc = fenced_device_register(xml, true);
pcmk__xml_free(xml);
if (rc != pcmk_rc_ok) {
exit_code = CRM_EX_FATAL;
crm_crit("Cannot register watchdog pseudo fence agent: %s",
pcmk_rc_str(rc));
stonith_shutdown(0);
}
}
} else if (fenced_has_watchdog_device()) {
/* be silent if no device - todo parameter to stonith_device_remove */
stonith_device_remove(STONITH_WATCHDOG_ID, true);
}
}
/*!
* \internal
* \brief Query the full CIB
*
* \return Standard Pacemaker return code
*/
static int
fenced_query_cib(void)
{
int rc = pcmk_ok;
crm_trace("Re-requesting full CIB");
rc = cib_api->cmds->query(cib_api, NULL, &local_cib, cib_sync_call);
rc = pcmk_legacy2rc(rc);
if (rc == pcmk_rc_ok) {
pcmk__assert(local_cib != NULL);
} else {
crm_err("Couldn't retrieve the CIB: %s " QB_XS " rc=%d",
pcmk_rc_str(rc), rc);
}
return rc;
}
static void
update_fencing_topology(const char *event, xmlNode *msg)
{
xmlNode *wrapper = pcmk__xe_first_child(msg, PCMK__XE_CIB_UPDATE_RESULT,
NULL, NULL);
xmlNode *patchset = pcmk__xe_first_child(wrapper, NULL, NULL, NULL);
int format = 1;
int add[] = { 0, 0, 0 };
int del[] = { 0, 0, 0 };
CRM_CHECK(patchset != NULL, return);
crm_element_value_int(patchset, PCMK_XA_FORMAT, &format);
if (format != 2) {
crm_warn("Unknown patch format: %d", format);
return;
}
- xml_patch_versions(patchset, add, del);
+ pcmk__xml_patchset_versions(patchset, del, add);
for (xmlNode *change = pcmk__xe_first_child(patchset, NULL, NULL, NULL);
change != NULL; change = pcmk__xe_next(change, NULL)) {
const char *op = crm_element_value(change, PCMK_XA_OPERATION);
const char *xpath = crm_element_value(change, PCMK_XA_PATH);
if (op == NULL) {
continue;
}
if (strstr(xpath, "/" PCMK_XE_FENCING_LEVEL) != NULL) {
// Change to a specific entry
crm_trace("Handling %s operation %d.%d.%d for %s",
op, add[0], add[1], add[2], xpath);
if (strcmp(op, PCMK_VALUE_DELETE) == 0) {
/* We have only path and ID, which is not enough info to remove
* a specific entry. Re-initialize the whole topology.
*/
crm_info("Re-initializing fencing topology after %s operation "
"%d.%d.%d for %s",
op, add[0], add[1], add[2], xpath);
fencing_topology_init();
return;
}
if (strcmp(op, PCMK_VALUE_CREATE) == 0) {
fenced_register_level(change->children, NULL);
} else if (strcmp(op, PCMK_VALUE_MODIFY) == 0) {
xmlNode *match = pcmk__xe_first_child(change,
PCMK_XE_CHANGE_RESULT,
NULL, NULL);
if (match != NULL) {
remove_topology_level(match->children);
fenced_register_level(match->children, NULL);
}
}
continue;
}
if (strstr(xpath, "/" PCMK_XE_FENCING_TOPOLOGY) != NULL) {
// Change to the topology in general
crm_info("Re-initializing fencing topology after top-level "
"%s operation %d.%d.%d for %s",
op, add[0], add[1], add[2], xpath);
fencing_topology_init();
return;
}
if ((strstr(xpath, "/" PCMK_XE_CONFIGURATION) != NULL)
&& (pcmk__xe_first_child(change, PCMK_XE_FENCING_TOPOLOGY, NULL,
NULL) != NULL)
&& pcmk__str_any_of(op, PCMK_VALUE_CREATE, PCMK_VALUE_DELETE,
NULL)) {
// Topology was created or entire configuration section was deleted
crm_info("Re-initializing fencing topology after top-level "
"%s operation %d.%d.%d for %s",
op, add[0], add[1], add[2], xpath);
fencing_topology_init();
return;
}
crm_trace("Nothing for us in %s operation %d.%d.%d for %s",
op, add[0], add[1], add[2], xpath);
}
}
static void
update_cib_cache_cb(const char *event, xmlNode * msg)
{
xmlNode *patchset = NULL;
long long timeout_ms_saved = stonith_watchdog_timeout_ms;
bool need_full_refresh = false;
if(!have_cib_devices) {
crm_trace("Skipping updates until we get a full dump");
return;
} else if(msg == NULL) {
crm_trace("Missing %s update", event);
return;
}
/* Maintain a local copy of the CIB so that we have full access
* to device definitions, location constraints, and node attributes
*/
if (local_cib != NULL) {
int rc = pcmk_ok;
xmlNode *wrapper = NULL;
crm_element_value_int(msg, PCMK__XA_CIB_RC, &rc);
if (rc != pcmk_ok) {
return;
}
wrapper = pcmk__xe_first_child(msg, PCMK__XE_CIB_UPDATE_RESULT, NULL,
NULL);
patchset = pcmk__xe_first_child(wrapper, NULL, NULL, NULL);
rc = xml_apply_patchset(local_cib, patchset, TRUE);
switch (rc) {
case pcmk_ok:
case -pcmk_err_old_data:
/* @TODO Full refresh (with or without query) in case of
* -pcmk_err_old_data? It seems wrong to call
* stonith_device_remove() based on primitive deletion in an
* old diff.
*/
break;
case -pcmk_err_diff_resync:
case -pcmk_err_diff_failed:
crm_notice("[%s] Patch aborted: %s (%d)", event, pcmk_strerror(rc), rc);
pcmk__xml_free(local_cib);
local_cib = NULL;
break;
default:
crm_warn("[%s] ABORTED: %s (%d)", event, pcmk_strerror(rc), rc);
pcmk__xml_free(local_cib);
local_cib = NULL;
}
}
if (local_cib == NULL) {
if (fenced_query_cib() != pcmk_rc_ok) {
return;
}
need_full_refresh = true;
}
pcmk__refresh_node_caches_from_cib(local_cib);
update_stonith_watchdog_timeout_ms(local_cib);
if (timeout_ms_saved != stonith_watchdog_timeout_ms) {
need_full_refresh = true;
}
if (need_full_refresh) {
fencing_topology_init();
cib_devices_update();
} else {
// Partial refresh
update_fencing_topology(event, msg);
update_cib_stonith_devices(patchset);
}
watchdog_device_update();
}
static void
init_cib_cache_cb(xmlNode * msg, int call_id, int rc, xmlNode * output, void *user_data)
{
crm_info("Updating device list from CIB");
have_cib_devices = TRUE;
local_cib = pcmk__xml_copy(NULL, output);
pcmk__refresh_node_caches_from_cib(local_cib);
update_stonith_watchdog_timeout_ms(local_cib);
fencing_topology_init();
cib_devices_update();
watchdog_device_update();
}
static void
cib_connection_destroy(gpointer user_data)
{
if (stonith_shutdown_flag) {
crm_info("Connection to the CIB manager closed");
return;
} else {
crm_crit("Lost connection to the CIB manager, shutting down");
}
if (cib_api) {
cib_api->cmds->signoff(cib_api);
}
stonith_shutdown(0);
}
/*!
* \internal
* \brief Disconnect from CIB manager
*/
void
fenced_cib_cleanup(void)
{
if (cib_api != NULL) {
cib_api->cmds->del_notify_callback(cib_api, PCMK__VALUE_CIB_DIFF_NOTIFY,
update_cib_cache_cb);
cib__clean_up_connection(&cib_api);
}
pcmk__xml_free(local_cib);
local_cib = NULL;
}
void
setup_cib(void)
{
int rc, retries = 0;
cib_api = cib_new();
if (cib_api == NULL) {
crm_err("No connection to the CIB manager");
return;
}
do {
sleep(retries);
rc = cib_api->cmds->signon(cib_api, crm_system_name, cib_command);
} while (rc == -ENOTCONN && ++retries < 5);
if (rc != pcmk_ok) {
crm_err("Could not connect to the CIB manager: %s (%d)", pcmk_strerror(rc), rc);
return;
}
rc = cib_api->cmds->add_notify_callback(cib_api,
PCMK__VALUE_CIB_DIFF_NOTIFY,
update_cib_cache_cb);
if (rc != pcmk_ok) {
crm_err("Could not set CIB notification callback");
return;
}
rc = cib_api->cmds->query(cib_api, NULL, NULL, cib_none);
cib_api->cmds->register_callback(cib_api, rc, 120, FALSE, NULL,
"init_cib_cache_cb", init_cib_cache_cb);
cib_api->cmds->set_connection_dnotify(cib_api, cib_connection_destroy);
crm_info("Watching for fencing topology changes");
}
diff --git a/include/crm/common/xml_internal.h b/include/crm/common/xml_internal.h
index e0bf139476..6a269fcba5 100644
--- a/include/crm/common/xml_internal.h
+++ b/include/crm/common/xml_internal.h
@@ -1,454 +1,446 @@
/*
* Copyright 2017-2025 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU Lesser General Public License
* version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
*/
#ifndef PCMK__CRM_COMMON_XML_INTERNAL__H
#define PCMK__CRM_COMMON_XML_INTERNAL__H
/*
* Internal-only wrappers for and extensions to libxml2 (libxslt)
*/
#include <stdlib.h>
#include <stdint.h> // uint32_t
#include <stdio.h>
#include <crm/crm.h> /* transitively imports qblog.h */
#include <crm/common/output_internal.h>
#include <crm/common/xml_names.h> // PCMK_XA_ID, PCMK_XE_CLONE
// This file is a wrapper for other {xml_*,xpath}_internal.h headers
#include <crm/common/xml_comment_internal.h>
#include <crm/common/xml_element_internal.h>
#include <crm/common/xml_idref_internal.h>
#include <crm/common/xml_io_internal.h>
#include <crm/common/xml_names_internal.h>
#include <crm/common/xpath_internal.h>
#include <libxml/relaxng.h>
#ifdef __cplusplus
extern "C" {
#endif
/*!
* \brief Base for directing lib{xml2,xslt} log into standard libqb backend
*
* This macro implements the core of what can be needed for directing
* libxml2 or libxslt error messaging into standard, preconfigured
* libqb-backed log stream.
*
* It's a bit unfortunate that libxml2 (and more sparsely, also libxslt)
* emits a single message by chunks (location is emitted separatedly from
* the message itself), so we have to take the effort to combine these
* chunks back to single message. Whether to do this or not is driven
* with \p dechunk toggle.
*
* The form of a macro was chosen for implicit deriving of __FILE__, etc.
* and also because static dechunking buffer should be differentiated per
* library (here we assume different functions referring to this macro
* will not ever be using both at once), preferably also per-library
* context of use to avoid clashes altogether.
*
* Note that we cannot use qb_logt, because callsite data have to be known
* at the moment of compilation, which it is not always the case -- xml_log
* (and unfortunately there's no clear explanation of the fail to compile).
*
* Also note that there's no explicit guard against said libraries producing
* never-newline-terminated chunks (which would just keep consuming memory),
* as it's quite improbable. Termination of the program in between the
* same-message chunks will raise a flag with valgrind and the likes, though.
*
* And lastly, regarding how dechunking combines with other non-message
* parameters -- for \p priority, most important running specification
* wins (possibly elevated to LOG_ERR in case of nonconformance with the
* newline-termination "protocol"), \p dechunk is expected to always be
* on once it was at the start, and the rest (\p postemit and \p prefix)
* are picked directly from the last chunk entry finalizing the message
* (also reasonable to always have it the same with all related entries).
*
* \param[in] priority Syslog priority for the message to be logged
* \param[in] dechunk Whether to dechunk new-line terminated message
* \param[in] postemit Code to be executed once message is sent out
* \param[in] prefix How to prefix the message or NULL for raw passing
* \param[in] fmt Format string as with printf-like functions
* \param[in] ap Variable argument list to supplement \p fmt format string
*/
#define PCMK__XML_LOG_BASE(priority, dechunk, postemit, prefix, fmt, ap) \
do { \
if (!(dechunk) && (prefix) == NULL) { /* quick pass */ \
qb_log_from_external_source_va(__func__, __FILE__, (fmt), \
(priority), __LINE__, 0, (ap)); \
(void) (postemit); \
} else { \
int CXLB_len = 0; \
char *CXLB_buf = NULL; \
static int CXLB_buffer_len = 0; \
static char *CXLB_buffer = NULL; \
static uint8_t CXLB_priority = 0; \
\
CXLB_len = vasprintf(&CXLB_buf, (fmt), (ap)); \
\
if (CXLB_len <= 0 || CXLB_buf[CXLB_len - 1] == '\n' || !(dechunk)) { \
if (CXLB_len < 0) { \
CXLB_buf = (char *) "LOG CORRUPTION HAZARD"; /*we don't modify*/\
CXLB_priority = QB_MIN(CXLB_priority, LOG_ERR); \
} else if (CXLB_len > 0 /* && (dechunk) */ \
&& CXLB_buf[CXLB_len - 1] == '\n') { \
CXLB_buf[CXLB_len - 1] = '\0'; \
} \
if (CXLB_buffer) { \
qb_log_from_external_source(__func__, __FILE__, "%s%s%s", \
CXLB_priority, __LINE__, 0, \
(prefix) != NULL ? (prefix) : "", \
CXLB_buffer, CXLB_buf); \
free(CXLB_buffer); \
} else { \
qb_log_from_external_source(__func__, __FILE__, "%s%s", \
(priority), __LINE__, 0, \
(prefix) != NULL ? (prefix) : "", \
CXLB_buf); \
} \
if (CXLB_len < 0) { \
CXLB_buf = NULL; /* restore temporary override */ \
} \
CXLB_buffer = NULL; \
CXLB_buffer_len = 0; \
(void) (postemit); \
\
} else if (CXLB_buffer == NULL) { \
CXLB_buffer_len = CXLB_len; \
CXLB_buffer = CXLB_buf; \
CXLB_buf = NULL; \
CXLB_priority = (priority); /* remember as a running severest */ \
\
} else { \
CXLB_buffer = realloc(CXLB_buffer, 1 + CXLB_buffer_len + CXLB_len); \
memcpy(CXLB_buffer + CXLB_buffer_len, CXLB_buf, CXLB_len); \
CXLB_buffer_len += CXLB_len; \
CXLB_buffer[CXLB_buffer_len] = '\0'; \
CXLB_priority = QB_MIN(CXLB_priority, (priority)); /* severest? */ \
} \
free(CXLB_buf); \
} \
} while (0)
/*!
* \internal
* \brief Bit flags to control format in XML logs and dumps
*/
enum pcmk__xml_fmt_options {
//! Exclude certain XML attributes (for calculating digests)
pcmk__xml_fmt_filtered = (1 << 0),
//! Include indentation and newlines
pcmk__xml_fmt_pretty = (1 << 1),
//! Include the opening tag of an XML element, and include XML comments
pcmk__xml_fmt_open = (1 << 3),
//! Include the children of an XML element
pcmk__xml_fmt_children = (1 << 4),
//! Include the closing tag of an XML element
pcmk__xml_fmt_close = (1 << 5),
// @COMPAT Can we start including text nodes unconditionally?
//! Include XML text nodes
pcmk__xml_fmt_text = (1 << 6),
};
int pcmk__xml_show(pcmk__output_t *out, const char *prefix, const xmlNode *data,
int depth, uint32_t options);
int pcmk__xml_show_changes(pcmk__output_t *out, const xmlNode *xml);
enum pcmk__xml_artefact_ns {
pcmk__xml_artefact_ns_legacy_rng = 1,
pcmk__xml_artefact_ns_legacy_xslt,
pcmk__xml_artefact_ns_base_rng,
pcmk__xml_artefact_ns_base_xslt,
};
void pcmk__strip_xml_text(xmlNode *xml);
/*!
* \internal
* \brief Indicators of which XML characters to escape
*
* XML allows the escaping of special characters by replacing them with entity
* references (for example, <tt>"""</tt>) or character references (for
* example, <tt>" "</tt>).
*
* The special characters <tt>'&'</tt> (except as the beginning of an entity
* reference) and <tt>'<'</tt> are not allowed in their literal forms in XML
* character data. Character data is non-markup text (for example, the content
* of a text node). <tt>'>'</tt> is allowed under most circumstances; we escape
* it for safety and symmetry.
*
* For more details, see the "Character Data and Markup" section of the XML
* spec, currently section 2.4:
* https://www.w3.org/TR/xml/#dt-markup
*
* Attribute values are handled specially.
* * If an attribute value is delimited by single quotes, then single quotes
* must be escaped within the value.
* * Similarly, if an attribute value is delimited by double quotes, then double
* quotes must be escaped within the value.
* * A conformant XML processor replaces a literal whitespace character (tab,
* newline, carriage return, space) in an attribute value with a space
* (\c '#x20') character. However, a reference to a whitespace character (for
* example, \c "
" for \c '\n') does not get replaced.
* * For more details, see the "Attribute-Value Normalization" section of the
* XML spec, currently section 3.3.3. Note that the default attribute type
* is CDATA; we don't deal with NMTOKENS, etc.:
* https://www.w3.org/TR/xml/#AVNormalize
*
* Pacemaker always delimits attribute values with double quotes, so there's no
* need to escape single quotes.
*
* Newlines and tabs should be escaped in attribute values when XML is
* serialized to text, so that future parsing preserves them rather than
* normalizing them to spaces.
*
* We always escape carriage returns, so that they're not converted to spaces
* during attribute-value normalization and because displaying them as literals
* is messy.
*/
enum pcmk__xml_escape_type {
/*!
* For text nodes.
* * Escape \c '<', \c '>', and \c '&' using entity references.
* * Do not escape \c '\n' and \c '\t'.
* * Escape other non-printing characters using character references.
*/
pcmk__xml_escape_text,
/*!
* For attribute values.
* * Escape \c '<', \c '>', \c '&', and \c '"' using entity references.
* * Escape \c '\n', \c '\t', and other non-printing characters using
* character references.
*/
pcmk__xml_escape_attr,
/* @COMPAT Drop escaping of at least '\n' and '\t' for
* pcmk__xml_escape_attr_pretty when openstack-info, openstack-floating-ip,
* and openstack-virtual-ip resource agents no longer depend on it.
*
* At time of writing, openstack-info may set a multiline value for the
* openstack_ports node attribute. The other two agents query the value and
* require it to be on one line with no spaces.
*/
/*!
* For attribute values displayed in text output delimited by double quotes.
* * Escape \c '\n' as \c "\\n"
* * Escape \c '\r' as \c "\\r"
* * Escape \c '\t' as \c "\\t"
* * Escape \c '"' as \c "\\""
*/
pcmk__xml_escape_attr_pretty,
};
bool pcmk__xml_needs_escape(const char *text, enum pcmk__xml_escape_type type);
char *pcmk__xml_escape(const char *text, enum pcmk__xml_escape_type type);
/*!
* \internal
* \brief Get the root directory to scan XML artefacts of given kind for
*
* \param[in] ns governs the hierarchy nesting against the inherent root dir
*
* \return root directory to scan XML artefacts of given kind for
*/
char *
pcmk__xml_artefact_root(enum pcmk__xml_artefact_ns ns);
/*!
* \internal
* \brief Get the fully unwrapped path to particular XML artifact (RNG/XSLT)
*
* \param[in] ns denotes path forming details (parent dir, suffix)
* \param[in] filespec symbolic file specification to be combined with
* #artefact_ns to form the final path
* \return unwrapped path to particular XML artifact (RNG/XSLT)
*/
char *pcmk__xml_artefact_path(enum pcmk__xml_artefact_ns ns,
const char *filespec);
/*!
* \internal
* \brief Return first non-text child node of an XML node
*
* \param[in] parent XML node to check
*
* \return First non-text child node of \p parent (or NULL if none)
*/
static inline xmlNode *
pcmk__xml_first_child(const xmlNode *parent)
{
xmlNode *child = (parent? parent->children : NULL);
while (child && (child->type == XML_TEXT_NODE)) {
child = child->next;
}
return child;
}
/*!
* \internal
* \brief Return next non-text sibling node of an XML node
*
* \param[in] child XML node to check
*
* \return Next non-text sibling of \p child (or NULL if none)
*/
static inline xmlNode *
pcmk__xml_next(const xmlNode *child)
{
xmlNode *next = (child? child->next : NULL);
while (next && (next->type == XML_TEXT_NODE)) {
next = next->next;
}
return next;
}
void pcmk__xml_free(xmlNode *xml);
void pcmk__xml_free_doc(xmlDoc *doc);
xmlNode *pcmk__xml_copy(xmlNode *parent, xmlNode *src);
/*!
* \internal
* \brief Flags for operations affecting XML attributes
*/
enum pcmk__xa_flags {
//! Flag has no effect
pcmk__xaf_none = 0U,
//! Don't overwrite existing values
pcmk__xaf_no_overwrite = (1U << 0),
/*!
* Treat values as score updates where possible (see
* \c pcmk__xe_set_score())
*/
pcmk__xaf_score_update = (1U << 1),
};
void pcmk__xml_sanitize_id(char *id);
/* internal XML-related utilities */
/*!
* \internal
* \brief Flags related to XML change tracking and ACLs
*/
enum pcmk__xml_flags {
//! This flag has no effect
pcmk__xf_none = UINT32_C(0),
/*!
* Node was created or modified, or one of its descendants was created,
* modified, moved, or deleted.
*/
pcmk__xf_dirty = (UINT32_C(1) << 0),
//! Node was deleted (set for attribute only)
pcmk__xf_deleted = (UINT32_C(1) << 1),
//! Node was created
pcmk__xf_created = (UINT32_C(1) << 2),
//! Node was modified
pcmk__xf_modified = (UINT32_C(1) << 3),
/*!
* \brief Tracking is enabled (set for document only)
*
* Call \c pcmk__xml_commit_changes() before setting this flag if a clean
* start for tracking is needed.
*/
pcmk__xf_tracking = (UINT32_C(1) << 4),
//! Skip counting this node when getting a node's position among siblings
pcmk__xf_skip = (UINT32_C(1) << 6),
//! Node was moved
pcmk__xf_moved = (UINT32_C(1) << 7),
//! ACLs are enabled (set for document only)
pcmk__xf_acl_enabled = (UINT32_C(1) << 8),
/* @TODO Consider splitting the ACL permission flags (pcmk__xf_acl_read,
* pcmk__xf_acl_write, pcmk__xf_acl_write, and pcmk__xf_acl_create) into a
* separate enum and reserving this enum for tracking-related flags.
*
* The ACL permission flags have various meanings in different contexts (for
* example, what permission an ACL grants or denies; what permissions the
* current ACL user has for a given XML node; and possibly others). And
* for xml_acl_t objects, they're used in exclusive mode (exactly one is
* set), rather than as flags.
*/
//! ACL read permission
pcmk__xf_acl_read = (UINT32_C(1) << 9),
//! ACL write permission (implies read permission in most or all contexts)
pcmk__xf_acl_write = (UINT32_C(1) << 10),
//! ACL deny permission (that is, no permission)
pcmk__xf_acl_deny = (UINT32_C(1) << 11),
/*!
* ACL create permission for attributes (if attribute exists, this is mapped
* to \c pcmk__xf_acl_write)
*/
pcmk__xf_acl_create = (UINT32_C(1) << 12),
//! ACLs deny the user access (set for document only)
pcmk__xf_acl_denied = (UINT32_C(1) << 13),
//! Ignore attribute moves within an element (set for document only)
pcmk__xf_ignore_attr_pos = (UINT32_C(1) << 14),
};
void pcmk__xml_doc_set_flags(xmlDoc *doc, uint32_t flags);
bool pcmk__xml_doc_all_flags_set(const xmlDoc *xml, uint32_t flags);
void pcmk__xml_commit_changes(xmlDoc *doc);
void pcmk__xml_mark_changes(xmlNode *old_xml, xmlNode *new_xml);
bool pcmk__xml_tree_foreach(xmlNode *xml, bool (*fn)(xmlNode *, void *),
void *user_data);
static inline const char *
pcmk__xml_attr_value(const xmlAttr *attr)
{
return ((attr == NULL) || (attr->children == NULL))? NULL
: (const char *) attr->children->content;
}
-/*!
- * \internal
- * \brief Check whether a given CIB element was modified in a CIB patchset
- *
- * \param[in] patchset CIB XML patchset
- * \param[in] element XML tag of CIB element to check (\c NULL is equivalent
- * to \c PCMK_XE_CIB). Supported values include any CIB
- * element supported by \c pcmk__cib_abs_xpath_for().
- *
- * \return \c true if \p element was modified, or \c false otherwise
- */
+int pcmk__xml_patchset_versions(const xmlNode *patchset, int source[3],
+ int target[3]);
+
bool pcmk__cib_element_in_patchset(const xmlNode *patchset,
const char *element);
#ifdef __cplusplus
}
#endif
#endif // PCMK__CRM_COMMON_XML_INTERNAL__H
diff --git a/lib/cib/cib_utils.c b/lib/cib/cib_utils.c
index 26a16a7f62..9127e04b7c 100644
--- a/lib/cib/cib_utils.c
+++ b/lib/cib/cib_utils.c
@@ -1,954 +1,954 @@
/*
* Original copyright 2004 International Business Machines
* Later changes copyright 2008-2025 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU Lesser General Public License
* version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
*/
#include <crm_internal.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <sys/utsname.h>
#include <glib.h>
#include <crm/crm.h>
#include <crm/cib/internal.h>
#include <crm/common/cib_internal.h>
#include <crm/common/xml.h>
#include <crm/common/xml_internal.h>
gboolean
cib_version_details(xmlNode * cib, int *admin_epoch, int *epoch, int *updates)
{
*epoch = -1;
*updates = -1;
*admin_epoch = -1;
if (cib == NULL) {
return FALSE;
} else {
crm_element_value_int(cib, PCMK_XA_EPOCH, epoch);
crm_element_value_int(cib, PCMK_XA_NUM_UPDATES, updates);
crm_element_value_int(cib, PCMK_XA_ADMIN_EPOCH, admin_epoch);
}
return TRUE;
}
gboolean
cib_diff_version_details(xmlNode * diff, int *admin_epoch, int *epoch, int *updates,
int *_admin_epoch, int *_epoch, int *_updates)
{
int add[] = { 0, 0, 0 };
int del[] = { 0, 0, 0 };
- xml_patch_versions(diff, add, del);
+ pcmk__xml_patchset_versions(diff, del, add);
*admin_epoch = add[0];
*epoch = add[1];
*updates = add[2];
*_admin_epoch = del[0];
*_epoch = del[1];
*_updates = del[2];
return TRUE;
}
/*!
* \internal
* \brief Get the XML patchset from a CIB diff notification
*
* \param[in] msg CIB diff notification
* \param[out] patchset Where to store XML patchset
*
* \return Standard Pacemaker return code
*/
int
cib__get_notify_patchset(const xmlNode *msg, const xmlNode **patchset)
{
int rc = pcmk_err_generic;
xmlNode *wrapper = NULL;
pcmk__assert(patchset != NULL);
*patchset = NULL;
if (msg == NULL) {
crm_err("CIB diff notification received with no XML");
return ENOMSG;
}
if ((crm_element_value_int(msg, PCMK__XA_CIB_RC, &rc) != 0)
|| (rc != pcmk_ok)) {
crm_warn("Ignore failed CIB update: %s " QB_XS " rc=%d",
pcmk_strerror(rc), rc);
crm_log_xml_debug(msg, "failed");
return pcmk_legacy2rc(rc);
}
wrapper = pcmk__xe_first_child(msg, PCMK__XE_CIB_UPDATE_RESULT, NULL, NULL);
*patchset = pcmk__xe_first_child(wrapper, NULL, NULL, NULL);
if (*patchset == NULL) {
crm_err("CIB diff notification received with no patchset");
return ENOMSG;
}
return pcmk_rc_ok;
}
/*!
* \brief Create XML for a new (empty) CIB
*
* \param[in] cib_epoch What to use as \c PCMK_XA_EPOCH CIB attribute
*
* \return Newly created XML for empty CIB
*
* \note It is the caller's responsibility to free the result with
* \c pcmk__xml_free().
*/
xmlNode *
createEmptyCib(int cib_epoch)
{
xmlNode *cib_root = NULL, *config = NULL;
cib_root = pcmk__xe_create(NULL, PCMK_XE_CIB);
crm_xml_add(cib_root, PCMK_XA_CRM_FEATURE_SET, CRM_FEATURE_SET);
crm_xml_add(cib_root, PCMK_XA_VALIDATE_WITH, pcmk__highest_schema_name());
crm_xml_add_int(cib_root, PCMK_XA_EPOCH, cib_epoch);
crm_xml_add_int(cib_root, PCMK_XA_NUM_UPDATES, 0);
crm_xml_add_int(cib_root, PCMK_XA_ADMIN_EPOCH, 0);
config = pcmk__xe_create(cib_root, PCMK_XE_CONFIGURATION);
pcmk__xe_create(cib_root, PCMK_XE_STATUS);
pcmk__xe_create(config, PCMK_XE_CRM_CONFIG);
pcmk__xe_create(config, PCMK_XE_NODES);
pcmk__xe_create(config, PCMK_XE_RESOURCES);
pcmk__xe_create(config, PCMK_XE_CONSTRAINTS);
#if PCMK__RESOURCE_STICKINESS_DEFAULT != 0
{
xmlNode *rsc_defaults = pcmk__xe_create(config, PCMK_XE_RSC_DEFAULTS);
xmlNode *meta = pcmk__xe_create(rsc_defaults, PCMK_XE_META_ATTRIBUTES);
xmlNode *nvpair = pcmk__xe_create(meta, PCMK_XE_NVPAIR);
crm_xml_add(meta, PCMK_XA_ID, "build-resource-defaults");
crm_xml_add(nvpair, PCMK_XA_ID, "build-" PCMK_META_RESOURCE_STICKINESS);
crm_xml_add(nvpair, PCMK_XA_NAME, PCMK_META_RESOURCE_STICKINESS);
crm_xml_add_int(nvpair, PCMK_XA_VALUE,
PCMK__RESOURCE_STICKINESS_DEFAULT);
}
#endif
return cib_root;
}
static bool
cib_acl_enabled(xmlNode *xml, const char *user)
{
bool rc = FALSE;
if(pcmk_acl_required(user)) {
const char *value = NULL;
GHashTable *options = pcmk__strkey_table(free, free);
cib_read_config(options, xml);
value = pcmk__cluster_option(options, PCMK_OPT_ENABLE_ACL);
rc = crm_is_true(value);
g_hash_table_destroy(options);
}
crm_trace("CIB ACL is %s", rc ? "enabled" : "disabled");
return rc;
}
/*!
* \internal
* \brief Determine whether to perform operations on a scratch copy of the CIB
*
* \param[in] op CIB operation
* \param[in] section CIB section
* \param[in] call_options CIB call options
*
* \return \p true if we should make a copy of the CIB, or \p false otherwise
*/
static bool
should_copy_cib(const char *op, const char *section, int call_options)
{
if (pcmk_is_set(call_options, cib_dryrun)) {
// cib_dryrun implies a scratch copy by definition; no side effects
return true;
}
if (pcmk__str_eq(op, PCMK__CIB_REQUEST_COMMIT_TRANSACT, pcmk__str_none)) {
/* Commit-transaction must make a copy for atomicity. We must revert to
* the original CIB if the entire transaction cannot be applied
* successfully.
*/
return true;
}
if (pcmk_is_set(call_options, cib_transaction)) {
/* If cib_transaction is set, then we're in the process of committing a
* transaction. The commit-transaction request already made a scratch
* copy, and we're accumulating changes in that copy.
*/
return false;
}
if (pcmk__str_eq(section, PCMK_XE_STATUS, pcmk__str_none)) {
/* Copying large CIBs accounts for a huge percentage of our CIB usage,
* and this avoids some of it.
*
* @TODO: Is this safe? See discussion at
* https://github.com/ClusterLabs/pacemaker/pull/3094#discussion_r1211400690.
*/
return false;
}
// Default behavior is to operate on a scratch copy
return true;
}
int
cib_perform_op(cib_t *cib, const char *op, uint32_t call_options,
cib__op_fn_t fn, bool is_query, const char *section,
xmlNode *req, xmlNode *input, bool manage_counters,
bool *config_changed, xmlNode **current_cib,
xmlNode **result_cib, xmlNode **diff, xmlNode **output)
{
int rc = pcmk_ok;
bool check_schema = true;
bool make_copy = true;
xmlNode *top = NULL;
xmlNode *scratch = NULL;
xmlNode *patchset_cib = NULL;
xmlNode *local_diff = NULL;
const char *user = crm_element_value(req, PCMK__XA_CIB_USER);
const bool enable_acl = cib_acl_enabled(*current_cib, user);
bool with_digest = false;
crm_trace("Begin %s%s%s op",
(pcmk_is_set(call_options, cib_dryrun)? "dry run of " : ""),
(is_query? "read-only " : ""), op);
CRM_CHECK(output != NULL, return -ENOMSG);
CRM_CHECK(current_cib != NULL, return -ENOMSG);
CRM_CHECK(result_cib != NULL, return -ENOMSG);
CRM_CHECK(config_changed != NULL, return -ENOMSG);
if(output) {
*output = NULL;
}
*result_cib = NULL;
*config_changed = false;
if (fn == NULL) {
return -EINVAL;
}
if (is_query) {
xmlNode *cib_ro = *current_cib;
xmlNode *cib_filtered = NULL;
if (enable_acl
&& xml_acl_filtered_copy(user, *current_cib, *current_cib,
&cib_filtered)) {
if (cib_filtered == NULL) {
crm_debug("Pre-filtered the entire cib");
return -EACCES;
}
cib_ro = cib_filtered;
crm_log_xml_trace(cib_ro, "filtered");
}
rc = (*fn) (op, call_options, section, req, input, cib_ro, result_cib, output);
if(output == NULL || *output == NULL) {
/* nothing */
} else if(cib_filtered == *output) {
cib_filtered = NULL; /* Let them have this copy */
} else if (*output == *current_cib) {
/* They already know not to free it */
} else if(cib_filtered && (*output)->doc == cib_filtered->doc) {
/* We're about to free the document of which *output is a part */
*output = pcmk__xml_copy(NULL, *output);
} else if ((*output)->doc == (*current_cib)->doc) {
/* Give them a copy they can free */
*output = pcmk__xml_copy(NULL, *output);
}
pcmk__xml_free(cib_filtered);
return rc;
}
make_copy = should_copy_cib(op, section, call_options);
if (!make_copy) {
/* Conditional on v2 patch style */
scratch = *current_cib;
// Make a copy of the top-level element to store version details
top = pcmk__xe_create(NULL, (const char *) scratch->name);
pcmk__xe_copy_attrs(top, scratch, pcmk__xaf_none);
patchset_cib = top;
pcmk__xml_commit_changes(scratch->doc);
pcmk__xml_doc_set_flags(scratch->doc, pcmk__xf_tracking);
if (enable_acl) {
pcmk__enable_acl(*current_cib, scratch, user);
}
rc = (*fn) (op, call_options, section, req, input, scratch, &scratch, output);
/* If scratch points to a new object now (for example, after an erase
* operation), then *current_cib should point to the same object.
*
* @TODO Enable tracking and ACLs and calculate changes? Change tracking
* and unpacked ACLs didn't carry over to new object.
*/
*current_cib = scratch;
} else {
scratch = pcmk__xml_copy(NULL, *current_cib);
patchset_cib = *current_cib;
pcmk__xml_doc_set_flags(scratch->doc, pcmk__xf_tracking);
if (enable_acl) {
pcmk__enable_acl(*current_cib, scratch, user);
}
rc = (*fn) (op, call_options, section, req, input, *current_cib,
&scratch, output);
/* @TODO This appears to be a hack to determine whether scratch points
* to a new object now, without saving the old pointer (which may be
* invalid now) for comparison. Confirm this, and check more clearly.
*/
if (!pcmk__xml_doc_all_flags_set(scratch->doc, pcmk__xf_tracking)) {
crm_trace("Inferring changes after %s op", op);
pcmk__xml_commit_changes(scratch->doc);
if (enable_acl) {
pcmk__enable_acl(*current_cib, scratch, user);
}
pcmk__xml_mark_changes(*current_cib, scratch);
}
CRM_CHECK(*current_cib != scratch, return -EINVAL);
}
xml_acl_disable(scratch); /* Allow the system to make any additional changes */
if (rc == pcmk_ok && scratch == NULL) {
rc = -EINVAL;
goto done;
} else if(rc == pcmk_ok && xml_acl_denied(scratch)) {
crm_trace("ACL rejected part or all of the proposed changes");
rc = -EACCES;
goto done;
} else if (rc != pcmk_ok) {
goto done;
}
/* If the CIB is from a file, we don't need to check that the feature set is
* supported. All we care about in that case is the schema version, which
* is checked elsewhere.
*/
if (scratch && (cib == NULL || cib->variant != cib_file)) {
const char *new_version = crm_element_value(scratch, PCMK_XA_CRM_FEATURE_SET);
rc = pcmk__check_feature_set(new_version);
if (rc != pcmk_rc_ok) {
crm_err("Discarding update with feature set '%s' greater than "
"our own '%s'", new_version, CRM_FEATURE_SET);
rc = pcmk_rc2legacy(rc);
goto done;
}
}
if (patchset_cib != NULL) {
int old = 0;
int new = 0;
crm_element_value_int(scratch, PCMK_XA_ADMIN_EPOCH, &new);
crm_element_value_int(patchset_cib, PCMK_XA_ADMIN_EPOCH, &old);
if (old > new) {
crm_err("%s went backwards: %d -> %d (Opts: %#x)",
PCMK_XA_ADMIN_EPOCH, old, new, call_options);
crm_log_xml_warn(req, "Bad Op");
crm_log_xml_warn(input, "Bad Data");
rc = -pcmk_err_old_data;
} else if (old == new) {
crm_element_value_int(scratch, PCMK_XA_EPOCH, &new);
crm_element_value_int(patchset_cib, PCMK_XA_EPOCH, &old);
if (old > new) {
crm_err("%s went backwards: %d -> %d (Opts: %#x)",
PCMK_XA_EPOCH, old, new, call_options);
crm_log_xml_warn(req, "Bad Op");
crm_log_xml_warn(input, "Bad Data");
rc = -pcmk_err_old_data;
}
}
}
crm_trace("Massaging CIB contents");
pcmk__strip_xml_text(scratch);
if (make_copy) {
static time_t expires = 0;
time_t tm_now = time(NULL);
if (expires < tm_now) {
expires = tm_now + 60; /* Validate clients are correctly applying v2-style diffs at most once a minute */
with_digest = true;
}
}
local_diff = xml_create_patchset(0, patchset_cib, scratch,
config_changed, manage_counters);
pcmk__log_xml_changes(LOG_TRACE, scratch);
pcmk__xml_commit_changes(scratch->doc);
if(local_diff) {
patchset_process_digest(local_diff, patchset_cib, scratch, with_digest);
pcmk__log_xml_patchset(LOG_INFO, local_diff);
crm_log_xml_trace(local_diff, "raw patch");
}
if (make_copy && (local_diff != NULL)) {
// Original to compare against doesn't exist
pcmk__if_tracing(
{
// Validate the calculated patch set
int test_rc = pcmk_ok;
int format = 1;
xmlNode *cib_copy = pcmk__xml_copy(NULL, patchset_cib);
crm_element_value_int(local_diff, PCMK_XA_FORMAT, &format);
test_rc = xml_apply_patchset(cib_copy, local_diff,
manage_counters);
if (test_rc != pcmk_ok) {
save_xml_to_file(cib_copy, "PatchApply:calculated", NULL);
save_xml_to_file(patchset_cib, "PatchApply:input", NULL);
save_xml_to_file(scratch, "PatchApply:actual", NULL);
save_xml_to_file(local_diff, "PatchApply:diff", NULL);
crm_err("v%d patchset error, patch failed to apply: %s "
"(%d)",
format, pcmk_rc_str(pcmk_legacy2rc(test_rc)),
test_rc);
}
pcmk__xml_free(cib_copy);
},
{}
);
}
if (pcmk__str_eq(section, PCMK_XE_STATUS, pcmk__str_casei)) {
/* Throttle the amount of costly validation we perform due to status updates
* a) we don't really care whats in the status section
* b) we don't validate any of its contents at the moment anyway
*/
check_schema = false;
}
/* === scratch must not be modified after this point ===
* Exceptions, anything in:
static filter_t filter[] = {
{ 0, PCMK_XA_CRM_DEBUG_ORIGIN },
{ 0, PCMK_XA_CIB_LAST_WRITTEN },
{ 0, PCMK_XA_UPDATE_ORIGIN },
{ 0, PCMK_XA_UPDATE_CLIENT },
{ 0, PCMK_XA_UPDATE_USER },
};
*/
if (*config_changed && !pcmk_is_set(call_options, cib_no_mtime)) {
const char *schema = crm_element_value(scratch, PCMK_XA_VALIDATE_WITH);
if (schema == NULL) {
rc = -pcmk_err_cib_corrupt;
}
pcmk__xe_add_last_written(scratch);
pcmk__warn_if_schema_deprecated(schema);
/* Make values of origin, client, and user in scratch match
* the ones in req (if the schema allows the attributes)
*/
if (pcmk__cmp_schemas_by_name(schema, "pacemaker-1.2") >= 0) {
const char *origin = crm_element_value(req, PCMK__XA_SRC);
const char *client = crm_element_value(req,
PCMK__XA_CIB_CLIENTNAME);
if (origin != NULL) {
crm_xml_add(scratch, PCMK_XA_UPDATE_ORIGIN, origin);
} else {
pcmk__xe_remove_attr(scratch, PCMK_XA_UPDATE_ORIGIN);
}
if (client != NULL) {
crm_xml_add(scratch, PCMK_XA_UPDATE_CLIENT, user);
} else {
pcmk__xe_remove_attr(scratch, PCMK_XA_UPDATE_CLIENT);
}
if (user != NULL) {
crm_xml_add(scratch, PCMK_XA_UPDATE_USER, user);
} else {
pcmk__xe_remove_attr(scratch, PCMK_XA_UPDATE_USER);
}
}
}
crm_trace("Perform validation: %s", pcmk__btoa(check_schema));
if ((rc == pcmk_ok) && check_schema
&& !pcmk__configured_schema_validates(scratch)) {
rc = -pcmk_err_schema_validation;
}
done:
*result_cib = scratch;
/* @TODO: This may not work correctly with !make_copy, since we don't
* keep the original CIB.
*/
if ((rc != pcmk_ok) && cib_acl_enabled(patchset_cib, user)
&& xml_acl_filtered_copy(user, patchset_cib, scratch, result_cib)) {
if (*result_cib == NULL) {
crm_debug("Pre-filtered the entire cib result");
}
pcmk__xml_free(scratch);
}
if(diff) {
*diff = local_diff;
} else {
pcmk__xml_free(local_diff);
}
pcmk__xml_free(top);
crm_trace("Done");
return rc;
}
int
cib__create_op(cib_t *cib, const char *op, const char *host,
const char *section, xmlNode *data, int call_options,
const char *user_name, const char *client_name,
xmlNode **op_msg)
{
CRM_CHECK((cib != NULL) && (op_msg != NULL), return -EPROTO);
*op_msg = pcmk__xe_create(NULL, PCMK__XE_CIB_COMMAND);
cib->call_id++;
if (cib->call_id < 1) {
cib->call_id = 1;
}
crm_xml_add(*op_msg, PCMK__XA_T, PCMK__VALUE_CIB);
crm_xml_add(*op_msg, PCMK__XA_CIB_OP, op);
crm_xml_add(*op_msg, PCMK__XA_CIB_HOST, host);
crm_xml_add(*op_msg, PCMK__XA_CIB_SECTION, section);
crm_xml_add(*op_msg, PCMK__XA_CIB_USER, user_name);
crm_xml_add(*op_msg, PCMK__XA_CIB_CLIENTNAME, client_name);
crm_xml_add_int(*op_msg, PCMK__XA_CIB_CALLID, cib->call_id);
crm_trace("Sending call options: %.8lx, %d", (long)call_options, call_options);
crm_xml_add_int(*op_msg, PCMK__XA_CIB_CALLOPT, call_options);
if (data != NULL) {
xmlNode *wrapper = pcmk__xe_create(*op_msg, PCMK__XE_CIB_CALLDATA);
pcmk__xml_copy(wrapper, data);
}
return pcmk_ok;
}
/*!
* \internal
* \brief Check whether a CIB request is supported in a transaction
*
* \param[in] request CIB request
*
* \return Standard Pacemaker return code
*/
static int
validate_transaction_request(const xmlNode *request)
{
const char *op = crm_element_value(request, PCMK__XA_CIB_OP);
const char *host = crm_element_value(request, PCMK__XA_CIB_HOST);
const cib__operation_t *operation = NULL;
int rc = cib__get_operation(op, &operation);
if (rc != pcmk_rc_ok) {
// cib__get_operation() logs error
return rc;
}
if (!pcmk_is_set(operation->flags, cib__op_attr_transaction)) {
crm_err("Operation %s is not supported in CIB transactions", op);
return EOPNOTSUPP;
}
if (host != NULL) {
crm_err("Operation targeting a specific node (%s) is not supported in "
"a CIB transaction",
host);
return EOPNOTSUPP;
}
return pcmk_rc_ok;
}
/*!
* \internal
* \brief Append a CIB request to a CIB transaction
*
* \param[in,out] cib CIB client whose transaction to extend
* \param[in,out] request Request to add to transaction
*
* \return Legacy Pacemaker return code
*/
int
cib__extend_transaction(cib_t *cib, xmlNode *request)
{
int rc = pcmk_rc_ok;
pcmk__assert((cib != NULL) && (request != NULL));
rc = validate_transaction_request(request);
if ((rc == pcmk_rc_ok) && (cib->transaction == NULL)) {
rc = pcmk_rc_no_transaction;
}
if (rc == pcmk_rc_ok) {
pcmk__xml_copy(cib->transaction, request);
} else {
const char *op = crm_element_value(request, PCMK__XA_CIB_OP);
const char *client_id = NULL;
cib->cmds->client_id(cib, NULL, &client_id);
crm_err("Failed to add '%s' operation to transaction for client %s: %s",
op, pcmk__s(client_id, "(unidentified)"), pcmk_rc_str(rc));
crm_log_xml_info(request, "failed");
}
return pcmk_rc2legacy(rc);
}
void
cib_native_callback(cib_t * cib, xmlNode * msg, int call_id, int rc)
{
xmlNode *output = NULL;
cib_callback_client_t *blob = NULL;
if (msg != NULL) {
xmlNode *wrapper = NULL;
crm_element_value_int(msg, PCMK__XA_CIB_RC, &rc);
crm_element_value_int(msg, PCMK__XA_CIB_CALLID, &call_id);
wrapper = pcmk__xe_first_child(msg, PCMK__XE_CIB_CALLDATA, NULL, NULL);
output = pcmk__xe_first_child(wrapper, NULL, NULL, NULL);
}
blob = cib__lookup_id(call_id);
if (blob == NULL) {
crm_trace("No callback found for call %d", call_id);
}
if (cib == NULL) {
crm_debug("No cib object supplied");
}
if (rc == -pcmk_err_diff_resync) {
/* This is an internal value that clients do not and should not care about */
rc = pcmk_ok;
}
if (blob && blob->callback && (rc == pcmk_ok || blob->only_success == FALSE)) {
crm_trace("Invoking callback %s for call %d",
pcmk__s(blob->id, "without ID"), call_id);
blob->callback(msg, call_id, rc, output, blob->user_data);
} else if ((cib != NULL) && (rc != pcmk_ok)) {
crm_warn("CIB command failed: %s", pcmk_strerror(rc));
crm_log_xml_debug(msg, "Failed CIB Update");
}
/* This may free user_data, so do it after the callback */
if (blob) {
remove_cib_op_callback(call_id, FALSE);
}
crm_trace("OP callback activated for %d", call_id);
}
void
cib_native_notify(gpointer data, gpointer user_data)
{
xmlNode *msg = user_data;
cib_notify_client_t *entry = data;
const char *event = NULL;
if (msg == NULL) {
crm_warn("Skipping callback - NULL message");
return;
}
event = crm_element_value(msg, PCMK__XA_SUBT);
if (entry == NULL) {
crm_warn("Skipping callback - NULL callback client");
return;
} else if (entry->callback == NULL) {
crm_warn("Skipping callback - NULL callback");
return;
} else if (!pcmk__str_eq(entry->event, event, pcmk__str_casei)) {
crm_trace("Skipping callback - event mismatch %p/%s vs. %s", entry, entry->event, event);
return;
}
crm_trace("Invoking callback for %p/%s event...", entry, event);
entry->callback(event, msg);
crm_trace("Callback invoked...");
}
gboolean
cib_read_config(GHashTable * options, xmlNode * current_cib)
{
xmlNode *config = NULL;
crm_time_t *now = NULL;
if (options == NULL || current_cib == NULL) {
return FALSE;
}
now = crm_time_new(NULL);
g_hash_table_remove_all(options);
config = pcmk_find_cib_element(current_cib, PCMK_XE_CRM_CONFIG);
if (config) {
pcmk_rule_input_t rule_input = {
.now = now,
};
pcmk_unpack_nvpair_blocks(config, PCMK_XE_CLUSTER_PROPERTY_SET,
PCMK_VALUE_CIB_BOOTSTRAP_OPTIONS, &rule_input,
options, NULL);
}
pcmk__validate_cluster_options(options);
crm_time_free(now);
return TRUE;
}
int
cib_internal_op(cib_t * cib, const char *op, const char *host,
const char *section, xmlNode * data,
xmlNode ** output_data, int call_options, const char *user_name)
{
int (*delegate)(cib_t *cib, const char *op, const char *host,
const char *section, xmlNode *data, xmlNode **output_data,
int call_options, const char *user_name) = NULL;
if (cib == NULL) {
return -EINVAL;
}
delegate = cib->delegate_fn;
if (delegate == NULL) {
return -EPROTONOSUPPORT;
}
if (user_name == NULL) {
user_name = getenv("CIB_user");
}
return delegate(cib, op, host, section, data, output_data, call_options, user_name);
}
/*!
* \brief Apply a CIB update patch to a given CIB
*
* \param[in] event CIB update patch
* \param[in] input CIB to patch
* \param[out] output Resulting CIB after patch
* \param[in] level Log the patch at this log level (unless LOG_CRIT)
*
* \return Legacy Pacemaker return code
* \note sbd calls this function
*/
int
cib_apply_patch_event(xmlNode *event, xmlNode *input, xmlNode **output,
int level)
{
int rc = pcmk_err_generic;
xmlNode *wrapper = NULL;
xmlNode *diff = NULL;
pcmk__assert((event != NULL) && (input != NULL) && (output != NULL));
crm_element_value_int(event, PCMK__XA_CIB_RC, &rc);
wrapper = pcmk__xe_first_child(event, PCMK__XE_CIB_UPDATE_RESULT, NULL,
NULL);
diff = pcmk__xe_first_child(wrapper, NULL, NULL, NULL);
if (rc < pcmk_ok || diff == NULL) {
return rc;
}
if (level > LOG_CRIT) {
pcmk__log_xml_patchset(level, diff);
}
if (input != NULL) {
rc = cib_process_diff(NULL, cib_none, NULL, event, diff, input, output,
NULL);
if (rc != pcmk_ok) {
crm_debug("Update didn't apply: %s (%d) %p",
pcmk_strerror(rc), rc, *output);
if (rc == -pcmk_err_old_data) {
crm_trace("Masking error, we already have the supplied update");
return pcmk_ok;
}
pcmk__xml_free(*output);
*output = NULL;
return rc;
}
}
return rc;
}
#define log_signon_query_err(out, fmt, args...) do { \
if (out != NULL) { \
out->err(out, fmt, ##args); \
} else { \
crm_err(fmt, ##args); \
} \
} while (0)
int
cib__signon_query(pcmk__output_t *out, cib_t **cib, xmlNode **cib_object)
{
int rc = pcmk_rc_ok;
cib_t *cib_conn = NULL;
pcmk__assert(cib_object != NULL);
if (cib == NULL) {
cib_conn = cib_new();
} else {
if (*cib == NULL) {
*cib = cib_new();
}
cib_conn = *cib;
}
if (cib_conn == NULL) {
return ENOMEM;
}
if (cib_conn->state == cib_disconnected) {
rc = cib_conn->cmds->signon(cib_conn, crm_system_name, cib_command);
rc = pcmk_legacy2rc(rc);
}
if (rc != pcmk_rc_ok) {
log_signon_query_err(out, "Could not connect to the CIB: %s",
pcmk_rc_str(rc));
goto done;
}
if (out != NULL) {
out->transient(out, "Querying CIB...");
}
rc = cib_conn->cmds->query(cib_conn, NULL, cib_object, cib_sync_call);
rc = pcmk_legacy2rc(rc);
if (rc != pcmk_rc_ok) {
log_signon_query_err(out, "CIB query failed: %s", pcmk_rc_str(rc));
}
done:
if (cib == NULL) {
cib__clean_up_connection(&cib_conn);
}
if ((rc == pcmk_rc_ok) && (*cib_object == NULL)) {
return pcmk_rc_no_input;
}
return rc;
}
int
cib__signon_attempts(cib_t *cib, enum cib_conn_type type, int attempts)
{
int rc = pcmk_rc_ok;
crm_trace("Attempting connection to CIB manager (up to %d time%s)",
attempts, pcmk__plural_s(attempts));
for (int remaining = attempts - 1; remaining >= 0; --remaining) {
rc = cib->cmds->signon(cib, crm_system_name, type);
if ((rc == pcmk_rc_ok)
|| (remaining == 0)
|| ((errno != EAGAIN) && (errno != EALREADY))) {
break;
}
// Retry after soft error (interrupted by signal, etc.)
pcmk__sleep_ms((attempts - remaining) * 500);
crm_debug("Re-attempting connection to CIB manager (%d attempt%s remaining)",
remaining, pcmk__plural_s(remaining));
}
return rc;
}
int
cib__clean_up_connection(cib_t **cib)
{
int rc;
if (*cib == NULL) {
return pcmk_rc_ok;
}
rc = (*cib)->cmds->signoff(*cib);
cib_delete(*cib);
*cib = NULL;
return pcmk_legacy2rc(rc);
}
diff --git a/lib/common/patchset.c b/lib/common/patchset.c
index fb9edda3a4..894fd7ea24 100644
--- a/lib/common/patchset.c
+++ b/lib/common/patchset.c
@@ -1,886 +1,965 @@
/*
* Copyright 2004-2025 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU Lesser General Public License
* version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
*/
#include <crm_internal.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <time.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include <bzlib.h>
#include <libxml/tree.h> // xmlNode
#include <crm/crm.h>
#include <crm/common/cib_internal.h>
#include <crm/common/xml.h>
#include <crm/common/xml_internal.h> // CRM_XML_LOG_BASE, etc.
#include "crmcommon_private.h"
/* Add changes for specified XML to patchset.
* For patchset format, refer to diff schema.
*/
static void
add_xml_changes_to_patchset(xmlNode *xml, xmlNode *patchset)
{
xmlNode *cIter = NULL;
xmlAttr *pIter = NULL;
xmlNode *change = NULL;
xml_node_private_t *nodepriv = xml->_private;
const char *value = NULL;
if (nodepriv == NULL) {
/* Elements that shouldn't occur in a CIB don't have _private set. They
* should be stripped out, ignored, or have an error thrown by any code
* that processes their parent, so we ignore any changes to them.
*/
return;
}
// If this XML node is new, just report that
if (patchset && pcmk_is_set(nodepriv->flags, pcmk__xf_created)) {
GString *xpath = pcmk__element_xpath(xml->parent);
if (xpath != NULL) {
int position = pcmk__xml_position(xml, pcmk__xf_deleted);
change = pcmk__xe_create(patchset, PCMK_XE_CHANGE);
crm_xml_add(change, PCMK_XA_OPERATION, PCMK_VALUE_CREATE);
crm_xml_add(change, PCMK_XA_PATH, (const char *) xpath->str);
crm_xml_add_int(change, PCMK_XE_POSITION, position);
pcmk__xml_copy(change, xml);
g_string_free(xpath, TRUE);
}
return;
}
// Check each of the XML node's attributes for changes
for (pIter = pcmk__xe_first_attr(xml); pIter != NULL;
pIter = pIter->next) {
xmlNode *attr = NULL;
nodepriv = pIter->_private;
if (!pcmk_any_flags_set(nodepriv->flags, pcmk__xf_deleted|pcmk__xf_dirty)) {
continue;
}
if (change == NULL) {
GString *xpath = pcmk__element_xpath(xml);
if (xpath != NULL) {
change = pcmk__xe_create(patchset, PCMK_XE_CHANGE);
crm_xml_add(change, PCMK_XA_OPERATION, PCMK_VALUE_MODIFY);
crm_xml_add(change, PCMK_XA_PATH, (const char *) xpath->str);
change = pcmk__xe_create(change, PCMK_XE_CHANGE_LIST);
g_string_free(xpath, TRUE);
}
}
attr = pcmk__xe_create(change, PCMK_XE_CHANGE_ATTR);
crm_xml_add(attr, PCMK_XA_NAME, (const char *) pIter->name);
if (nodepriv->flags & pcmk__xf_deleted) {
crm_xml_add(attr, PCMK_XA_OPERATION, "unset");
} else {
crm_xml_add(attr, PCMK_XA_OPERATION, "set");
value = pcmk__xml_attr_value(pIter);
crm_xml_add(attr, PCMK_XA_VALUE, value);
}
}
if (change) {
xmlNode *result = NULL;
change = pcmk__xe_create(change->parent, PCMK_XE_CHANGE_RESULT);
result = pcmk__xe_create(change, (const char *)xml->name);
for (pIter = pcmk__xe_first_attr(xml); pIter != NULL;
pIter = pIter->next) {
nodepriv = pIter->_private;
if (!pcmk_is_set(nodepriv->flags, pcmk__xf_deleted)) {
value = crm_element_value(xml, (const char *) pIter->name);
crm_xml_add(result, (const char *)pIter->name, value);
}
}
}
// Now recursively do the same for each child node of this node
for (cIter = pcmk__xml_first_child(xml); cIter != NULL;
cIter = pcmk__xml_next(cIter)) {
add_xml_changes_to_patchset(cIter, patchset);
}
nodepriv = xml->_private;
if (patchset && pcmk_is_set(nodepriv->flags, pcmk__xf_moved)) {
GString *xpath = pcmk__element_xpath(xml);
crm_trace("%s.%s moved to position %d",
xml->name, pcmk__xe_id(xml),
pcmk__xml_position(xml, pcmk__xf_skip));
if (xpath != NULL) {
change = pcmk__xe_create(patchset, PCMK_XE_CHANGE);
crm_xml_add(change, PCMK_XA_OPERATION, PCMK_VALUE_MOVE);
crm_xml_add(change, PCMK_XA_PATH, (const char *) xpath->str);
crm_xml_add_int(change, PCMK_XE_POSITION,
pcmk__xml_position(xml, pcmk__xf_deleted));
g_string_free(xpath, TRUE);
}
}
}
static bool
is_config_change(xmlNode *xml)
{
GList *gIter = NULL;
xml_node_private_t *nodepriv = NULL;
xml_doc_private_t *docpriv;
xmlNode *config = pcmk__xe_first_child(xml, PCMK_XE_CONFIGURATION, NULL,
NULL);
if (config) {
nodepriv = config->_private;
}
if ((nodepriv != NULL) && pcmk_is_set(nodepriv->flags, pcmk__xf_dirty)) {
return TRUE;
}
if ((xml->doc != NULL) && (xml->doc->_private != NULL)) {
docpriv = xml->doc->_private;
for (gIter = docpriv->deleted_objs; gIter; gIter = gIter->next) {
pcmk__deleted_xml_t *deleted_obj = gIter->data;
if (strstr(deleted_obj->path,
"/" PCMK_XE_CIB "/" PCMK_XE_CONFIGURATION) != NULL) {
return TRUE;
}
}
}
return FALSE;
}
static xmlNode *
xml_create_patchset_v2(xmlNode *source, xmlNode *target)
{
int lpc = 0;
GList *gIter = NULL;
xml_doc_private_t *docpriv;
xmlNode *v = NULL;
xmlNode *version = NULL;
xmlNode *patchset = NULL;
const char *vfields[] = {
PCMK_XA_ADMIN_EPOCH,
PCMK_XA_EPOCH,
PCMK_XA_NUM_UPDATES,
};
pcmk__assert(target != NULL);
if (!pcmk__xml_doc_all_flags_set(target->doc, pcmk__xf_dirty)) {
return NULL;
}
pcmk__assert(target->doc != NULL);
docpriv = target->doc->_private;
patchset = pcmk__xe_create(NULL, PCMK_XE_DIFF);
crm_xml_add_int(patchset, PCMK_XA_FORMAT, 2);
version = pcmk__xe_create(patchset, PCMK_XE_VERSION);
v = pcmk__xe_create(version, PCMK_XE_SOURCE);
for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
const char *value = crm_element_value(source, vfields[lpc]);
if (value == NULL) {
value = "1";
}
crm_xml_add(v, vfields[lpc], value);
}
v = pcmk__xe_create(version, PCMK_XE_TARGET);
for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
const char *value = crm_element_value(target, vfields[lpc]);
if (value == NULL) {
value = "1";
}
crm_xml_add(v, vfields[lpc], value);
}
for (gIter = docpriv->deleted_objs; gIter; gIter = gIter->next) {
pcmk__deleted_xml_t *deleted_obj = gIter->data;
xmlNode *change = pcmk__xe_create(patchset, PCMK_XE_CHANGE);
crm_xml_add(change, PCMK_XA_OPERATION, PCMK_VALUE_DELETE);
crm_xml_add(change, PCMK_XA_PATH, deleted_obj->path);
if (deleted_obj->position >= 0) {
crm_xml_add_int(change, PCMK_XE_POSITION, deleted_obj->position);
}
}
add_xml_changes_to_patchset(target, patchset);
return patchset;
}
xmlNode *
xml_create_patchset(int format, xmlNode *source, xmlNode *target,
bool *config_changed, bool manage_version)
{
bool local_config_changed = false;
if (format == 0) {
format = 2;
}
if (format != 2) {
crm_err("Unknown patch format: %d", format);
return NULL;
}
xml_acl_disable(target);
if ((target == NULL)
|| !pcmk__xml_doc_all_flags_set(target->doc, pcmk__xf_dirty)) {
crm_trace("No change %d", format);
return NULL;
}
if (config_changed == NULL) {
config_changed = &local_config_changed;
}
*config_changed = is_config_change(target);
if (manage_version) {
int counter = 0;
if (*config_changed) {
crm_xml_add(target, PCMK_XA_NUM_UPDATES, "0");
crm_element_value_int(target, PCMK_XA_EPOCH, &counter);
crm_xml_add_int(target, PCMK_XA_EPOCH, counter + 1);
} else {
crm_element_value_int(target, PCMK_XA_NUM_UPDATES, &counter);
crm_xml_add_int(target, PCMK_XA_NUM_UPDATES, counter + 1);
}
}
return xml_create_patchset_v2(source, target);
}
void
patchset_process_digest(xmlNode *patch, xmlNode *source, xmlNode *target,
bool with_digest)
{
char *digest = NULL;
if ((patch == NULL) || (source == NULL) || (target == NULL)
|| !with_digest) {
return;
}
/* We should always call pcmk__xml_commit_changes() before calculating a
* digest. Otherwise, with an on-tracking dirty target, we could get a wrong
* digest.
*/
CRM_LOG_ASSERT(!pcmk__xml_doc_all_flags_set(target->doc, pcmk__xf_dirty));
digest = pcmk__digest_xml(target, true);
crm_xml_add(patch, PCMK__XA_DIGEST, digest);
free(digest);
return;
}
+/*!
+ * \internal
+ * \brief Get the source and target CIB versions from an XML patchset
+ *
+ * Each output object will contain, in order, the following version fields from
+ * the source and target, respectively:
+ * * \c PCMK_XA_ADMIN_EPOCH
+ * * \c PCMK_XA_EPOCH
+ * * \c PCMK_XA_NUM_UPDATES
+ *
+ * \param[in] patchset XML patchset
+ * \param[out] source Where to store versions from source CIB
+ * \param[out] target Where to store versions from target CIB
+ *
+ * \return Standard Pacemaker return code
+ */
+int
+pcmk__xml_patchset_versions(const xmlNode *patchset, int source[3],
+ int target[3])
+{
+ static const char *const vfields[] = {
+ PCMK_XA_ADMIN_EPOCH,
+ PCMK_XA_EPOCH,
+ PCMK_XA_NUM_UPDATES,
+ };
+
+ int format = 0;
+ const xmlNode *version = NULL;
+ const xmlNode *source_xml = NULL;
+ const xmlNode *target_xml = NULL;
+
+ CRM_CHECK((patchset != NULL) && (source != NULL) && (target != NULL),
+ return EINVAL);
+
+ crm_element_value_int(patchset, PCMK_XA_FORMAT, &format);
+ if (format != 2) {
+ crm_err("Unknown patch format: %d", format);
+ return EINVAL;
+ }
+
+ version = pcmk__xe_first_child(patchset, PCMK_XE_VERSION, NULL, NULL);
+ source_xml = pcmk__xe_first_child(version, PCMK_XE_SOURCE, NULL, NULL);
+ target_xml = pcmk__xe_first_child(version, PCMK_XE_TARGET, NULL, NULL);
+
+ if ((source_xml == NULL) || (target_xml == NULL)) {
+ return EINVAL;
+ }
+
+ for (int i = 0; i < PCMK__NELEM(vfields); i++) {
+ if (crm_element_value_int(source_xml, vfields[i], &(source[i])) != 0) {
+ return EINVAL;
+ }
+ crm_trace("Got %d for source[%s]", source[i], vfields[i]);
+
+ if (crm_element_value_int(target_xml, vfields[i], &(target[i])) != 0) {
+ return EINVAL;
+ }
+ crm_trace("Got %d for target[%s]", target[i], vfields[i]);
+ }
+
+ return pcmk_rc_ok;
+}
+
// Get CIB versions used for additions and deletions in a patchset
// Return value of true means failure; false means success
bool
xml_patch_versions(const xmlNode *patchset, int add[3], int del[3])
{
static const char *const vfields[] = {
PCMK_XA_ADMIN_EPOCH,
PCMK_XA_EPOCH,
PCMK_XA_NUM_UPDATES,
};
const xmlNode *version = pcmk__xe_first_child(patchset, PCMK_XE_VERSION,
NULL, NULL);
const xmlNode *source = pcmk__xe_first_child(version, PCMK_XE_SOURCE, NULL,
NULL);
const xmlNode *target = pcmk__xe_first_child(version, PCMK_XE_TARGET, NULL,
NULL);
int format = 1;
crm_element_value_int(patchset, PCMK_XA_FORMAT, &format);
if (format != 2) {
crm_err("Unknown patch format: %d", format);
return true;
}
if (source != NULL) {
for (int i = 0; i < PCMK__NELEM(vfields); i++) {
crm_element_value_int(source, vfields[i], &(del[i]));
crm_trace("Got %d for del[%s]", del[i], vfields[i]);
}
}
if (target != NULL) {
for (int i = 0; i < PCMK__NELEM(vfields); i++) {
crm_element_value_int(target, vfields[i], &(add[i]));
crm_trace("Got %d for add[%s]", add[i], vfields[i]);
}
}
return false;
}
/*!
* \internal
* \brief Check whether patchset can be applied to current CIB
*
* \param[in] xml Root of current CIB
* \param[in] patchset Patchset to check
*
* \return Standard Pacemaker return code
*/
static int
xml_patch_version_check(const xmlNode *xml, const xmlNode *patchset)
{
int lpc = 0;
bool changed = FALSE;
int this[] = { 0, 0, 0 };
int add[] = { 0, 0, 0 };
int del[] = { 0, 0, 0 };
+ int rc = pcmk_rc_ok;
const char *vfields[] = {
PCMK_XA_ADMIN_EPOCH,
PCMK_XA_EPOCH,
PCMK_XA_NUM_UPDATES,
};
for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
crm_element_value_int(xml, vfields[lpc], &(this[lpc]));
crm_trace("Got %d for this[%s]", this[lpc], vfields[lpc]);
if (this[lpc] < 0) {
this[lpc] = 0;
}
}
/* Set some defaults in case nothing is present */
add[0] = this[0];
add[1] = this[1];
add[2] = this[2] + 1;
for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
del[lpc] = this[lpc];
}
- xml_patch_versions(patchset, add, del);
+ rc = pcmk__xml_patchset_versions(patchset, del, add);
+ if (rc != pcmk_rc_ok) {
+ return rc;
+ }
for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
if (this[lpc] < del[lpc]) {
crm_debug("Current %s is too low (%d.%d.%d < %d.%d.%d --> %d.%d.%d)",
vfields[lpc], this[0], this[1], this[2],
del[0], del[1], del[2], add[0], add[1], add[2]);
return pcmk_rc_diff_resync;
} else if (this[lpc] > del[lpc]) {
crm_info("Current %s is too high (%d.%d.%d > %d.%d.%d --> %d.%d.%d) %p",
vfields[lpc], this[0], this[1], this[2],
del[0], del[1], del[2], add[0], add[1], add[2], patchset);
crm_log_xml_info(patchset, "OldPatch");
return pcmk_rc_old_data;
}
}
for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
if (add[lpc] > del[lpc]) {
changed = TRUE;
}
}
if (!changed) {
crm_notice("Versions did not change in patch %d.%d.%d",
add[0], add[1], add[2]);
return pcmk_rc_old_data;
}
crm_debug("Can apply patch %d.%d.%d to %d.%d.%d",
add[0], add[1], add[2], this[0], this[1], this[2]);
return pcmk_rc_ok;
}
// Return first child matching element name and optionally id or position
static xmlNode *
first_matching_xml_child(const xmlNode *parent, const char *name,
const char *id, int position)
{
xmlNode *cIter = NULL;
for (cIter = pcmk__xml_first_child(parent); cIter != NULL;
cIter = pcmk__xml_next(cIter)) {
if (strcmp((const char *) cIter->name, name) != 0) {
continue;
} else if (id) {
const char *cid = pcmk__xe_id(cIter);
if ((cid == NULL) || (strcmp(cid, id) != 0)) {
continue;
}
}
// "position" makes sense only for XML comments for now
if ((cIter->type == XML_COMMENT_NODE)
&& (position >= 0)
&& (pcmk__xml_position(cIter, pcmk__xf_skip) != position)) {
continue;
}
return cIter;
}
return NULL;
}
/*!
* \internal
* \brief Simplified, more efficient alternative to pcmk__xpath_find_one()
*
* \param[in] top Root of XML to search
* \param[in] key Search xpath
* \param[in] target_position If deleting, where to delete
*
* \return XML child matching xpath if found, NULL otherwise
*
* \note This only works on simplified xpaths found in v2 patchset diffs,
* i.e. the only allowed search predicate is [@id='XXX'].
*/
static xmlNode *
search_v2_xpath(const xmlNode *top, const char *key, int target_position)
{
xmlNode *target = (xmlNode *) top->doc;
const char *current = key;
char *section;
char *remainder;
char *id;
char *tag;
char *path = NULL;
int rc;
size_t key_len;
CRM_CHECK(key != NULL, return NULL);
key_len = strlen(key);
/* These are scanned from key after a slash, so they can't be bigger
* than key_len - 1 characters plus a null terminator.
*/
remainder = pcmk__assert_alloc(key_len, sizeof(char));
section = pcmk__assert_alloc(key_len, sizeof(char));
id = pcmk__assert_alloc(key_len, sizeof(char));
tag = pcmk__assert_alloc(key_len, sizeof(char));
do {
// Look for /NEXT_COMPONENT/REMAINING_COMPONENTS
rc = sscanf(current, "/%[^/]%s", section, remainder);
if (rc > 0) {
// Separate FIRST_COMPONENT into TAG[@id='ID']
int f = sscanf(section, "%[^[][@" PCMK_XA_ID "='%[^']", tag, id);
int current_position = -1;
/* The target position is for the final component tag, so only use
* it if there is nothing left to search after this component.
*/
if ((rc == 1) && (target_position >= 0)) {
current_position = target_position;
}
switch (f) {
case 1:
target = first_matching_xml_child(target, tag, NULL,
current_position);
break;
case 2:
target = first_matching_xml_child(target, tag, id,
current_position);
break;
default:
// This should not be possible
target = NULL;
break;
}
current = remainder;
}
// Continue if something remains to search, and we've matched so far
} while ((rc == 2) && target);
if (target) {
crm_trace("Found %s for %s",
(path = (char *) xmlGetNodePath(target)), key);
free(path);
} else {
crm_debug("No match for %s", key);
}
free(remainder);
free(section);
free(tag);
free(id);
return target;
}
typedef struct xml_change_obj_s {
const xmlNode *change;
xmlNode *match;
} xml_change_obj_t;
static gint
sort_change_obj_by_position(gconstpointer a, gconstpointer b)
{
const xml_change_obj_t *change_obj_a = a;
const xml_change_obj_t *change_obj_b = b;
int position_a = -1;
int position_b = -1;
crm_element_value_int(change_obj_a->change, PCMK_XE_POSITION, &position_a);
crm_element_value_int(change_obj_b->change, PCMK_XE_POSITION, &position_b);
if (position_a < position_b) {
return -1;
} else if (position_a > position_b) {
return 1;
}
return 0;
}
/*!
* \internal
* \brief Apply a version 2 patchset to an XML node
*
* \param[in,out] xml XML to apply patchset to
* \param[in] patchset Patchset to apply
*
* \return Standard Pacemaker return code
*/
static int
apply_v2_patchset(xmlNode *xml, const xmlNode *patchset)
{
int rc = pcmk_rc_ok;
const xmlNode *change = NULL;
GList *change_objs = NULL;
GList *gIter = NULL;
for (change = pcmk__xml_first_child(patchset); change != NULL;
change = pcmk__xml_next(change)) {
xmlNode *match = NULL;
const char *op = crm_element_value(change, PCMK_XA_OPERATION);
const char *xpath = crm_element_value(change, PCMK_XA_PATH);
int position = -1;
if (op == NULL) {
continue;
}
crm_trace("Processing %s %s", change->name, op);
/* PCMK_VALUE_DELETE changes for XML comments are generated with
* PCMK_XE_POSITION
*/
if (strcmp(op, PCMK_VALUE_DELETE) == 0) {
crm_element_value_int(change, PCMK_XE_POSITION, &position);
}
match = search_v2_xpath(xml, xpath, position);
crm_trace("Performing %s on %s with %p", op, xpath, match);
if ((match == NULL) && (strcmp(op, PCMK_VALUE_DELETE) == 0)) {
crm_debug("No %s match for %s in %p", op, xpath, xml->doc);
continue;
} else if (match == NULL) {
crm_err("No %s match for %s in %p", op, xpath, xml->doc);
rc = pcmk_rc_diff_failed;
continue;
} else if (pcmk__str_any_of(op,
PCMK_VALUE_CREATE, PCMK_VALUE_MOVE, NULL)) {
// Delay the adding of a PCMK_VALUE_CREATE object
xml_change_obj_t *change_obj =
pcmk__assert_alloc(1, sizeof(xml_change_obj_t));
change_obj->change = change;
change_obj->match = match;
change_objs = g_list_append(change_objs, change_obj);
if (strcmp(op, PCMK_VALUE_MOVE) == 0) {
// Temporarily put the PCMK_VALUE_MOVE object after the last sibling
if ((match->parent != NULL) && (match->parent->last != NULL)) {
xmlAddNextSibling(match->parent->last, match);
}
}
} else if (strcmp(op, PCMK_VALUE_DELETE) == 0) {
pcmk__xml_free(match);
} else if (strcmp(op, PCMK_VALUE_MODIFY) == 0) {
const xmlNode *child = pcmk__xe_first_child(change,
PCMK_XE_CHANGE_RESULT,
NULL, NULL);
const xmlNode *attrs = pcmk__xml_first_child(child);
if (attrs == NULL) {
rc = ENOMSG;
continue;
}
// Remove all attributes
pcmk__xe_remove_matching_attrs(match, false, NULL, NULL);
for (xmlAttrPtr pIter = pcmk__xe_first_attr(attrs); pIter != NULL;
pIter = pIter->next) {
const char *name = (const char *) pIter->name;
const char *value = pcmk__xml_attr_value(pIter);
crm_xml_add(match, name, value);
}
} else {
crm_err("Unknown operation: %s", op);
rc = pcmk_rc_diff_failed;
}
}
// Changes should be generated in the right order. Double checking.
change_objs = g_list_sort(change_objs, sort_change_obj_by_position);
for (gIter = change_objs; gIter; gIter = gIter->next) {
xml_change_obj_t *change_obj = gIter->data;
xmlNode *match = change_obj->match;
const char *op = NULL;
const char *xpath = NULL;
change = change_obj->change;
op = crm_element_value(change, PCMK_XA_OPERATION);
xpath = crm_element_value(change, PCMK_XA_PATH);
crm_trace("Continue performing %s on %s with %p", op, xpath, match);
if (strcmp(op, PCMK_VALUE_CREATE) == 0) {
int position = 0;
xmlNode *child = NULL;
xmlNode *match_child = NULL;
match_child = match->children;
crm_element_value_int(change, PCMK_XE_POSITION, &position);
while ((match_child != NULL)
&& (position != pcmk__xml_position(match_child, pcmk__xf_skip))) {
match_child = match_child->next;
}
child = pcmk__xml_copy(match, change->children);
if (match_child != NULL) {
crm_trace("Adding %s at position %d", child->name, position);
xmlAddPrevSibling(match_child, child);
} else {
crm_trace("Adding %s at position %d (end)",
child->name, position);
}
} else if (strcmp(op, PCMK_VALUE_MOVE) == 0) {
int position = 0;
crm_element_value_int(change, PCMK_XE_POSITION, &position);
if (position != pcmk__xml_position(match, pcmk__xf_skip)) {
xmlNode *match_child = NULL;
int p = position;
if (p > pcmk__xml_position(match, pcmk__xf_skip)) {
p++; // Skip ourselves
}
pcmk__assert(match->parent != NULL);
match_child = match->parent->children;
while ((match_child != NULL)
&& (p != pcmk__xml_position(match_child, pcmk__xf_skip))) {
match_child = match_child->next;
}
crm_trace("Moving %s to position %d (was %d, prev %p, %s %p)",
match->name, position,
pcmk__xml_position(match, pcmk__xf_skip),
match->prev, (match_child? "next":"last"),
(match_child? match_child : match->parent->last));
if (match_child) {
xmlAddPrevSibling(match_child, match);
} else {
pcmk__assert(match->parent->last != NULL);
xmlAddNextSibling(match->parent->last, match);
}
} else {
crm_trace("%s is already in position %d",
match->name, position);
}
if (position != pcmk__xml_position(match, pcmk__xf_skip)) {
crm_err("Moved %s.%s to position %d instead of %d (%p)",
match->name, pcmk__xe_id(match),
pcmk__xml_position(match, pcmk__xf_skip),
position, match->prev);
rc = pcmk_rc_diff_failed;
}
}
}
g_list_free_full(change_objs, free);
return rc;
}
int
xml_apply_patchset(xmlNode *xml, xmlNode *patchset, bool check_version)
{
int format = 1;
int rc = pcmk_ok;
xmlNode *old = NULL;
const char *digest = NULL;
if (patchset == NULL) {
return rc;
}
pcmk__log_xml_patchset(LOG_TRACE, patchset);
if (check_version) {
rc = pcmk_rc2legacy(xml_patch_version_check(xml, patchset));
if (rc != pcmk_ok) {
return rc;
}
}
digest = crm_element_value(patchset, PCMK__XA_DIGEST);
if (digest != NULL) {
/* Make original XML available for logging in case result doesn't have
* expected digest
*/
pcmk__if_tracing(old = pcmk__xml_copy(NULL, xml), {});
}
if (rc == pcmk_ok) {
crm_element_value_int(patchset, PCMK_XA_FORMAT, &format);
if (format != 2) {
crm_err("Unknown patch format: %d", format);
rc = -EINVAL;
} else {
rc = pcmk_rc2legacy(apply_v2_patchset(xml, patchset));
}
}
if ((rc == pcmk_ok) && (digest != NULL)) {
char *new_digest = NULL;
new_digest = pcmk__digest_xml(xml, true);
if (!pcmk__str_eq(new_digest, digest, pcmk__str_casei)) {
crm_info("v%d digest mis-match: expected %s, calculated %s",
format, digest, new_digest);
rc = -pcmk_err_diff_failed;
pcmk__if_tracing(
{
save_xml_to_file(old, "PatchDigest:input", NULL);
save_xml_to_file(xml, "PatchDigest:result", NULL);
save_xml_to_file(patchset, "PatchDigest:diff", NULL);
},
{}
);
} else {
crm_trace("v%d digest matched: expected %s, calculated %s",
format, digest, new_digest);
}
free(new_digest);
}
pcmk__xml_free(old);
return rc;
}
+/*!
+ * \internal
+ * \brief Check whether a given CIB element was modified in a CIB patchset
+ *
+ * \param[in] patchset CIB XML patchset
+ * \param[in] element XML tag of CIB element to check (\c NULL is equivalent
+ * to \c PCMK_XE_CIB). Supported values include any CIB
+ * element supported by \c pcmk__cib_abs_xpath_for().
+ *
+ * \retval \c true if \p element was modified
+ * \retval \c false otherwise
+ */
bool
pcmk__cib_element_in_patchset(const xmlNode *patchset, const char *element)
{
const char *element_xpath = pcmk__cib_abs_xpath_for(element);
const char *parent_xpath = pcmk_cib_parent_name_for(element);
char *element_regex = NULL;
bool rc = false;
int format = 1;
pcmk__assert(patchset != NULL);
crm_element_value_int(patchset, PCMK_XA_FORMAT, &format);
if (format != 2) {
crm_warn("Unknown patch format: %d", format);
return false;
}
CRM_CHECK(element_xpath != NULL, return false); // Unsupported element
/* Matches if and only if element_xpath is part of a changed path
* (supported values for element never contain XML IDs with schema
* validation enabled)
*
* @TODO Use POSIX word boundary instead of (/|$), if it works:
* https://www.regular-expressions.info/wordboundaries.html.
*/
element_regex = crm_strdup_printf("^%s(/|$)", element_xpath);
for (const xmlNode *change = pcmk__xe_first_child(patchset, PCMK_XE_CHANGE,
NULL, NULL);
change != NULL; change = pcmk__xe_next(change, PCMK_XE_CHANGE)) {
const char *op = crm_element_value(change, PCMK_XA_OPERATION);
const char *diff_xpath = crm_element_value(change, PCMK_XA_PATH);
if (pcmk__str_eq(diff_xpath, element_regex, pcmk__str_regex)) {
// Change to an existing element
rc = true;
break;
}
if (pcmk__str_eq(op, PCMK_VALUE_CREATE, pcmk__str_none)
&& pcmk__str_eq(diff_xpath, parent_xpath, pcmk__str_none)
&& pcmk__xe_is(pcmk__xe_first_child(change, NULL, NULL, NULL),
element)) {
// Newly added element
rc = true;
break;
}
}
free(element_regex);
return rc;
}
diff --git a/lib/common/patchset_display.c b/lib/common/patchset_display.c
index 5ddbdbc533..f3dabb43dd 100644
--- a/lib/common/patchset_display.c
+++ b/lib/common/patchset_display.c
@@ -1,326 +1,326 @@
/*
- * Copyright 2004-2024 the Pacemaker project contributors
+ * Copyright 2004-2025 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU Lesser General Public License
* version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
*/
#include <crm_internal.h>
#include <crm/common/xml.h>
#include "crmcommon_private.h"
/*!
* \internal
* \brief Output an XML patchset header
*
* This function parses a header from an XML patchset (a \c PCMK_XE_DIFF element
* and its children).
*
* All header lines contain three integers separated by dots, of the form
* <tt>{0}.{1}.{2}</tt>:
* * \p {0}: \c PCMK_XA_ADMIN_EPOCH
* * \p {1}: \c PCMK_XA_EPOCH
* * \p {2}: \c PCMK_XA_NUM_UPDATES
*
* Lines containing \p "---" describe removals and end with the patch format
* number. Lines containing \p "+++" describe additions and end with the patch
* digest.
*
* \param[in,out] out Output object
* \param[in] patchset XML patchset to output
*
* \return Standard Pacemaker return code
*
* \note This function produces output only for text-like formats.
*/
static int
xml_show_patchset_header(pcmk__output_t *out, const xmlNode *patchset)
{
int rc = pcmk_rc_no_output;
int add[] = { 0, 0, 0 };
int del[] = { 0, 0, 0 };
- xml_patch_versions(patchset, add, del);
+ pcmk__xml_patchset_versions(patchset, del, add);
if ((add[0] != del[0]) || (add[1] != del[1]) || (add[2] != del[2])) {
const char *fmt = crm_element_value(patchset, PCMK_XA_FORMAT);
const char *digest = crm_element_value(patchset, PCMK__XA_DIGEST);
out->info(out, "Diff: --- %d.%d.%d %s", del[0], del[1], del[2], fmt);
rc = out->info(out, "Diff: +++ %d.%d.%d %s",
add[0], add[1], add[2], digest);
} else if ((add[0] != 0) || (add[1] != 0) || (add[2] != 0)) {
rc = out->info(out, "Local-only Change: %d.%d.%d",
add[0], add[1], add[2]);
}
return rc;
}
/*!
* \internal
* \brief Output a user-friendly form of an XML patchset
*
* This function parses an XML patchset (a \c PCMK_XE_DIFF element and its
* children) into a user-friendly combined diff output.
*
* \param[in,out] out Output object
* \param[in] patchset XML patchset to output
*
* \return Standard Pacemaker return code
*
* \note This function produces output only for text-like formats.
*/
static int
xml_show_patchset(pcmk__output_t *out, const xmlNode *patchset)
{
int rc = xml_show_patchset_header(out, patchset);
int temp_rc = pcmk_rc_no_output;
for (const xmlNode *change = pcmk__xe_first_child(patchset, NULL, NULL,
NULL);
change != NULL; change = pcmk__xe_next(change, NULL)) {
const char *op = crm_element_value(change, PCMK_XA_OPERATION);
const char *xpath = crm_element_value(change, PCMK_XA_PATH);
if (op == NULL) {
continue;
}
if (strcmp(op, PCMK_VALUE_CREATE) == 0) {
char *prefix = crm_strdup_printf(PCMK__XML_PREFIX_CREATED " %s: ",
xpath);
temp_rc = pcmk__xml_show(out, prefix, change->children, 0,
pcmk__xml_fmt_pretty|pcmk__xml_fmt_open);
rc = pcmk__output_select_rc(rc, temp_rc);
// Overwrite all except the first two characters with spaces
for (char *ch = prefix + 2; *ch != '\0'; ch++) {
*ch = ' ';
}
temp_rc = pcmk__xml_show(out, prefix, change->children, 0,
pcmk__xml_fmt_pretty
|pcmk__xml_fmt_children
|pcmk__xml_fmt_close);
rc = pcmk__output_select_rc(rc, temp_rc);
free(prefix);
} else if (strcmp(op, PCMK_VALUE_MOVE) == 0) {
const char *position = crm_element_value(change, PCMK_XE_POSITION);
temp_rc = out->info(out,
PCMK__XML_PREFIX_MOVED " %s moved to offset %s",
xpath, position);
rc = pcmk__output_select_rc(rc, temp_rc);
} else if (strcmp(op, PCMK_VALUE_MODIFY) == 0) {
xmlNode *clist = pcmk__xe_first_child(change, PCMK_XE_CHANGE_LIST,
NULL, NULL);
GString *buffer_set = NULL;
GString *buffer_unset = NULL;
for (const xmlNode *child = pcmk__xe_first_child(clist, NULL, NULL,
NULL);
child != NULL; child = pcmk__xe_next(child, NULL)) {
const char *name = crm_element_value(child, PCMK_XA_NAME);
op = crm_element_value(child, PCMK_XA_OPERATION);
if (op == NULL) {
continue;
}
if (strcmp(op, "set") == 0) {
const char *value = crm_element_value(child, PCMK_XA_VALUE);
pcmk__add_separated_word(&buffer_set, 256, "@", ", ");
pcmk__g_strcat(buffer_set, name, "=", value, NULL);
} else if (strcmp(op, "unset") == 0) {
pcmk__add_separated_word(&buffer_unset, 256, "@", ", ");
g_string_append(buffer_unset, name);
}
}
if (buffer_set != NULL) {
temp_rc = out->info(out, "+ %s: %s", xpath, buffer_set->str);
rc = pcmk__output_select_rc(rc, temp_rc);
g_string_free(buffer_set, TRUE);
}
if (buffer_unset != NULL) {
temp_rc = out->info(out, "-- %s: %s",
xpath, buffer_unset->str);
rc = pcmk__output_select_rc(rc, temp_rc);
g_string_free(buffer_unset, TRUE);
}
} else if (strcmp(op, PCMK_VALUE_DELETE) == 0) {
int position = -1;
crm_element_value_int(change, PCMK_XE_POSITION, &position);
if (position >= 0) {
temp_rc = out->info(out, "-- %s (%d)", xpath, position);
} else {
temp_rc = out->info(out, "-- %s", xpath);
}
rc = pcmk__output_select_rc(rc, temp_rc);
}
}
return rc;
}
/*!
* \internal
* \brief Output a user-friendly form of an XML patchset
*
* This function parses an XML patchset (a \c PCMK_XE_DIFF element and its
* children) into a user-friendly combined diff output.
*
* \param[in,out] out Output object
* \param[in] args Message-specific arguments
*
* \return Standard Pacemaker return code
*
* \note \p args should contain the following:
* -# XML patchset
*/
PCMK__OUTPUT_ARGS("xml-patchset", "const xmlNode *")
static int
xml_patchset_default(pcmk__output_t *out, va_list args)
{
const xmlNode *patchset = va_arg(args, const xmlNode *);
int format = 1;
if (patchset == NULL) {
crm_trace("Empty patch");
return pcmk_rc_no_output;
}
crm_element_value_int(patchset, PCMK_XA_FORMAT, &format);
if (format != 2) {
crm_err("Unknown patch format: %d", format);
return pcmk_rc_bad_xml_patch;
}
return xml_show_patchset(out, patchset);
}
/*!
* \internal
* \brief Output a user-friendly form of an XML patchset
*
* This function parses an XML patchset (a \c PCMK_XE_DIFF element and its
* children) into a user-friendly combined diff output.
*
* \param[in,out] out Output object
* \param[in] args Message-specific arguments
*
* \return Standard Pacemaker return code
*
* \note \p args should contain the following:
* -# XML patchset
*/
PCMK__OUTPUT_ARGS("xml-patchset", "const xmlNode *")
static int
xml_patchset_log(pcmk__output_t *out, va_list args)
{
static struct qb_log_callsite *patchset_cs = NULL;
const xmlNode *patchset = va_arg(args, const xmlNode *);
uint8_t log_level = pcmk__output_get_log_level(out);
int format = 1;
if (log_level == LOG_NEVER) {
return pcmk_rc_no_output;
}
if (patchset == NULL) {
crm_trace("Empty patch");
return pcmk_rc_no_output;
}
if (patchset_cs == NULL) {
patchset_cs = qb_log_callsite_get(__func__, __FILE__, "xml-patchset",
log_level, __LINE__,
crm_trace_nonlog);
}
if (!crm_is_callsite_active(patchset_cs, log_level, crm_trace_nonlog)) {
// Nothing would be logged, so skip all the work
return pcmk_rc_no_output;
}
crm_element_value_int(patchset, PCMK_XA_FORMAT, &format);
if (format != 2) {
crm_err("Unknown patch format: %d", format);
return pcmk_rc_bad_xml_patch;
}
return xml_show_patchset(out, patchset);
}
/*!
* \internal
* \brief Output an XML patchset
*
* This function outputs an XML patchset (a \c PCMK_XE_DIFF element and its
* children) without modification, as a CDATA block.
*
* \param[in,out] out Output object
* \param[in] args Message-specific arguments
*
* \return Standard Pacemaker return code
*
* \note \p args should contain the following:
* -# XML patchset
*/
PCMK__OUTPUT_ARGS("xml-patchset", "const xmlNode *")
static int
xml_patchset_xml(pcmk__output_t *out, va_list args)
{
const xmlNode *patchset = va_arg(args, const xmlNode *);
if (patchset != NULL) {
GString *buf = g_string_sized_new(1024);
pcmk__xml_string(patchset, pcmk__xml_fmt_pretty|pcmk__xml_fmt_text, buf,
0);
out->output_xml(out, PCMK_XE_XML_PATCHSET, buf->str);
g_string_free(buf, TRUE);
return pcmk_rc_ok;
}
crm_trace("Empty patch");
return pcmk_rc_no_output;
}
static pcmk__message_entry_t fmt_functions[] = {
{ "xml-patchset", "default", xml_patchset_default },
{ "xml-patchset", "log", xml_patchset_log },
{ "xml-patchset", "xml", xml_patchset_xml },
{ NULL, NULL, NULL }
};
/*!
* \internal
* \brief Register the formatting functions for XML patchsets
*
* \param[in,out] out Output object
*/
void
pcmk__register_patchset_messages(pcmk__output_t *out) {
pcmk__register_messages(out, fmt_functions);
}
diff --git a/tools/crm_diff.c b/tools/crm_diff.c
index 27eb6d155c..f5292e9e06 100644
--- a/tools/crm_diff.c
+++ b/tools/crm_diff.c
@@ -1,336 +1,336 @@
/*
* Copyright 2005-2025 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU General Public License version 2
* or later (GPLv2+) WITHOUT ANY WARRANTY.
*/
#include <crm_internal.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/param.h>
#include <sys/types.h>
#include <crm/crm.h>
#include <crm/common/cmdline_internal.h>
#include <crm/common/output_internal.h>
#include <crm/common/xml.h>
#include <crm/common/ipc.h>
#include <crm/cib.h>
#define SUMMARY "Compare two Pacemaker configurations (in XML format) to produce a custom diff-like output, " \
"or apply such an output as a patch"
struct {
gboolean apply;
gboolean as_cib;
gboolean no_version;
gboolean raw_original;
gboolean raw_new;
gboolean use_stdin;
char *xml_file_original;
char *xml_file_new;
} options;
gboolean new_string_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
gboolean original_string_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
gboolean patch_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
static GOptionEntry original_xml_entries[] = {
{ "original", 'o', 0, G_OPTION_ARG_STRING, &options.xml_file_original,
"XML is contained in the named file",
"FILE" },
{ "original-string", 'O', 0, G_OPTION_ARG_CALLBACK, original_string_cb,
"XML is contained in the supplied string",
"STRING" },
{ NULL }
};
static GOptionEntry operation_entries[] = {
{ "new", 'n', 0, G_OPTION_ARG_STRING, &options.xml_file_new,
"Compare the original XML to the contents of the named file",
"FILE" },
{ "new-string", 'N', 0, G_OPTION_ARG_CALLBACK, new_string_cb,
"Compare the original XML with the contents of the supplied string",
"STRING" },
{ "patch", 'p', 0, G_OPTION_ARG_CALLBACK, patch_cb,
"Patch the original XML with the contents of the named file",
"FILE" },
{ NULL }
};
static GOptionEntry addl_entries[] = {
{ "cib", 'c', 0, G_OPTION_ARG_NONE, &options.as_cib,
"Compare/patch the inputs as a CIB (includes versions details)",
NULL },
{ "stdin", 's', 0, G_OPTION_ARG_NONE, &options.use_stdin,
"",
NULL },
{ "no-version", 'u', 0, G_OPTION_ARG_NONE, &options.no_version,
"Generate the difference without versions details",
NULL },
{ NULL }
};
gboolean
new_string_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
options.raw_new = TRUE;
pcmk__str_update(&options.xml_file_new, optarg);
return TRUE;
}
gboolean
original_string_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
options.raw_original = TRUE;
pcmk__str_update(&options.xml_file_original, optarg);
return TRUE;
}
gboolean
patch_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
options.apply = TRUE;
pcmk__str_update(&options.xml_file_new, optarg);
return TRUE;
}
static void
print_patch(xmlNode *patch)
{
GString *buffer = g_string_sized_new(1024);
pcmk__xml_string(patch, pcmk__xml_fmt_pretty, buffer, 0);
printf("%s", buffer->str);
g_string_free(buffer, TRUE);
fflush(stdout);
}
// \return Standard Pacemaker return code
static int
apply_patch(xmlNode *input, xmlNode *patch, gboolean as_cib)
{
xmlNode *output = pcmk__xml_copy(NULL, input);
int rc = xml_apply_patchset(output, patch, as_cib);
rc = pcmk_legacy2rc(rc);
if (rc != pcmk_rc_ok) {
fprintf(stderr, "Could not apply patch: %s\n", pcmk_rc_str(rc));
pcmk__xml_free(output);
return rc;
}
if (output != NULL) {
char *buffer;
print_patch(output);
buffer = pcmk__digest_xml(output, true);
crm_trace("Digest: %s", pcmk__s(buffer, "<null>\n"));
free(buffer);
pcmk__xml_free(output);
}
return pcmk_rc_ok;
}
static void
log_patch_cib_versions(xmlNode *patch)
{
int add[] = { 0, 0, 0 };
int del[] = { 0, 0, 0 };
const char *fmt = NULL;
const char *digest = NULL;
- xml_patch_versions(patch, add, del);
+ pcmk__xml_patchset_versions(patch, del, add);
fmt = crm_element_value(patch, PCMK_XA_FORMAT);
digest = crm_element_value(patch, PCMK__XA_DIGEST);
if (add[2] != del[2] || add[1] != del[1] || add[0] != del[0]) {
crm_info("Patch: --- %d.%d.%d %s", del[0], del[1], del[2], fmt);
crm_info("Patch: +++ %d.%d.%d %s", add[0], add[1], add[2], digest);
}
}
// \return Standard Pacemaker return code
static int
generate_patch(xmlNode *object_original, xmlNode *object_new, const char *xml_file_new,
gboolean as_cib, gboolean no_version)
{
const char *vfields[] = {
PCMK_XA_ADMIN_EPOCH,
PCMK_XA_EPOCH,
PCMK_XA_NUM_UPDATES,
};
xmlNode *output = NULL;
/* If we're ignoring the version, make the version information
* identical, so it isn't detected as a change. */
if (no_version) {
int lpc;
for (lpc = 0; lpc < PCMK__NELEM(vfields); lpc++) {
crm_copy_xml_element(object_original, object_new, vfields[lpc]);
}
}
if (as_cib) {
pcmk__xml_doc_set_flags(object_new->doc, pcmk__xf_ignore_attr_pos);
}
pcmk__xml_mark_changes(object_original, object_new);
crm_log_xml_debug(object_new, (xml_file_new? xml_file_new: "target"));
output = xml_create_patchset(0, object_original, object_new, NULL, FALSE);
pcmk__log_xml_changes(LOG_INFO, object_new);
pcmk__xml_commit_changes(object_new->doc);
if (output == NULL) {
return pcmk_rc_ok; // No changes
}
patchset_process_digest(output, object_original, object_new, as_cib);
if (as_cib) {
log_patch_cib_versions(output);
} else if (no_version) {
pcmk__xml_free(pcmk__xe_first_child(output, PCMK_XE_VERSION, NULL,
NULL));
}
pcmk__log_xml_patchset(LOG_NOTICE, output);
print_patch(output);
pcmk__xml_free(output);
/* pcmk_rc_error means there's a non-empty diff.
* @COMPAT Choose a more descriptive return code, like one that maps to
* CRM_EX_DIGEST?
*/
return pcmk_rc_error;
}
static GOptionContext *
build_arg_context(pcmk__common_args_t *args) {
GOptionContext *context = NULL;
const char *description = "Examples:\n\n"
"Obtain the two different configuration files by running cibadmin on the two cluster setups to compare:\n\n"
"\t# cibadmin --query > cib-old.xml\n\n"
"\t# cibadmin --query > cib-new.xml\n\n"
"Calculate and save the difference between the two files:\n\n"
"\t# crm_diff --original cib-old.xml --new cib-new.xml > patch.xml\n\n"
"Apply the patch to the original file:\n\n"
"\t# crm_diff --original cib-old.xml --patch patch.xml > updated.xml\n\n"
"Apply the patch to the running cluster:\n\n"
"\t# cibadmin --patch -x patch.xml\n";
context = pcmk__build_arg_context(args, NULL, NULL, NULL);
g_option_context_set_description(context, description);
pcmk__add_arg_group(context, "xml", "Original XML:",
"Show original XML options", original_xml_entries);
pcmk__add_arg_group(context, "operation", "Operation:",
"Show operation options", operation_entries);
pcmk__add_arg_group(context, "additional", "Additional Options:",
"Show additional options", addl_entries);
return context;
}
int
main(int argc, char **argv)
{
xmlNode *object_original = NULL;
xmlNode *object_new = NULL;
crm_exit_t exit_code = CRM_EX_OK;
GError *error = NULL;
pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY);
gchar **processed_args = pcmk__cmdline_preproc(argv, "nopNO");
GOptionContext *context = build_arg_context(args);
int rc = pcmk_rc_ok;
if (!g_option_context_parse_strv(context, &processed_args, &error)) {
exit_code = CRM_EX_USAGE;
goto done;
}
pcmk__cli_init_logging("crm_diff", args->verbosity);
if (args->version) {
g_strfreev(processed_args);
pcmk__free_arg_context(context);
/* FIXME: When crm_diff is converted to use formatted output, this can go. */
pcmk__cli_help('v');
}
if (options.apply && options.no_version) {
fprintf(stderr, "warning: -u/--no-version ignored with -p/--patch\n");
} else if (options.as_cib && options.no_version) {
fprintf(stderr, "error: -u/--no-version incompatible with -c/--cib\n");
exit_code = CRM_EX_USAGE;
goto done;
}
if (options.raw_original) {
object_original = pcmk__xml_parse(options.xml_file_original);
} else if (options.use_stdin) {
fprintf(stderr, "Input first XML fragment:");
object_original = pcmk__xml_read(NULL);
} else if (options.xml_file_original != NULL) {
object_original = pcmk__xml_read(options.xml_file_original);
}
if (options.raw_new) {
object_new = pcmk__xml_parse(options.xml_file_new);
} else if (options.use_stdin) {
fprintf(stderr, "Input second XML fragment:");
object_new = pcmk__xml_read(NULL);
} else if (options.xml_file_new != NULL) {
object_new = pcmk__xml_read(options.xml_file_new);
}
if (object_original == NULL) {
fprintf(stderr, "Could not parse the first XML fragment\n");
exit_code = CRM_EX_DATAERR;
goto done;
}
if (object_new == NULL) {
fprintf(stderr, "Could not parse the second XML fragment\n");
exit_code = CRM_EX_DATAERR;
goto done;
}
if (options.apply) {
rc = apply_patch(object_original, object_new, options.as_cib);
} else {
rc = generate_patch(object_original, object_new, options.xml_file_new, options.as_cib, options.no_version);
}
exit_code = pcmk_rc2exitc(rc);
done:
g_strfreev(processed_args);
pcmk__free_arg_context(context);
free(options.xml_file_original);
free(options.xml_file_new);
pcmk__xml_free(object_original);
pcmk__xml_free(object_new);
pcmk__output_and_clear_error(&error, NULL);
crm_exit(exit_code);
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Thu, Jul 10, 2:29 AM (1 d, 15 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1992067
Default Alt Text
(156 KB)
Attached To
Mode
rP Pacemaker
Attached
Detach File
Event Timeline
Log In to Comment