Page MenuHomeClusterLabs Projects

No OneTemporary

diff --git a/daemons/based/based_common.c b/daemons/based/based_common.c
index 3b6832b23c..8146fc1a57 100644
--- a/daemons/based/based_common.c
+++ b/daemons/based/based_common.c
@@ -1,362 +1,362 @@
/*
* Copyright 2008-2022 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU General Public License version 2
* or later (GPLv2+) WITHOUT ANY WARRANTY.
*/
#include <crm_internal.h>
#include <sys/param.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <crm/crm.h>
#include <crm/cib.h>
#include <crm/msg_xml.h>
#include <crm/common/ipc.h>
#include <crm/cluster.h>
#include <crm/common/xml.h>
#include <pacemaker-based.h>
gboolean stand_alone = FALSE;
extern int cib_perform_command(xmlNode * request, xmlNode ** reply, xmlNode ** cib_diff,
gboolean privileged);
static xmlNode *
cib_prepare_common(xmlNode * root, const char *section)
{
xmlNode *data = NULL;
/* extract the CIB from the fragment */
if (root == NULL) {
return NULL;
} else if (pcmk__strcase_any_of(crm_element_name(root), XML_TAG_FRAGMENT,
F_CRM_DATA, F_CIB_CALLDATA, NULL)) {
data = first_named_child(root, XML_TAG_CIB);
} else {
data = root;
}
/* grab the section specified for the command */
if (section != NULL && data != NULL && pcmk__str_eq(crm_element_name(data), XML_TAG_CIB, pcmk__str_none)) {
data = pcmk_find_cib_element(data, section);
}
/* crm_log_xml_trace(root, "cib:input"); */
return data;
}
static int
cib_prepare_none(xmlNode * request, xmlNode ** data, const char **section)
{
*data = NULL;
*section = crm_element_value(request, F_CIB_SECTION);
return pcmk_ok;
}
static int
cib_prepare_data(xmlNode * request, xmlNode ** data, const char **section)
{
xmlNode *input_fragment = get_message_xml(request, F_CIB_CALLDATA);
*section = crm_element_value(request, F_CIB_SECTION);
*data = cib_prepare_common(input_fragment, *section);
/* crm_log_xml_debug(*data, "data"); */
return pcmk_ok;
}
static int
cib_prepare_sync(xmlNode * request, xmlNode ** data, const char **section)
{
*data = NULL;
*section = crm_element_value(request, F_CIB_SECTION);
return pcmk_ok;
}
static int
cib_prepare_diff(xmlNode * request, xmlNode ** data, const char **section)
{
xmlNode *input_fragment = NULL;
*data = NULL;
*section = NULL;
if (pcmk__xe_attr_is_true(request, F_CIB_GLOBAL_UPDATE)) {
input_fragment = get_message_xml(request, F_CIB_UPDATE_DIFF);
} else {
input_fragment = get_message_xml(request, F_CIB_CALLDATA);
}
CRM_CHECK(input_fragment != NULL, crm_log_xml_warn(request, "no input"));
*data = cib_prepare_common(input_fragment, NULL);
return pcmk_ok;
}
static int
cib_cleanup_query(int options, xmlNode ** data, xmlNode ** output)
{
CRM_LOG_ASSERT(*data == NULL);
if ((options & cib_no_children)
|| pcmk__str_eq(crm_element_name(*output), "xpath-query", pcmk__str_casei)) {
free_xml(*output);
}
return pcmk_ok;
}
static int
cib_cleanup_data(int options, xmlNode ** data, xmlNode ** output)
{
free_xml(*output);
*data = NULL;
return pcmk_ok;
}
static int
cib_cleanup_output(int options, xmlNode ** data, xmlNode ** output)
{
free_xml(*output);
return pcmk_ok;
}
static int
cib_cleanup_none(int options, xmlNode ** data, xmlNode ** output)
{
CRM_LOG_ASSERT(*data == NULL);
CRM_LOG_ASSERT(*output == NULL);
return pcmk_ok;
}
static cib_operation_t cib_server_ops[] = {
// Booleans are modifies_cib, needs_privileges, needs_quorum
{
NULL, FALSE, FALSE, FALSE,
cib_prepare_none, cib_cleanup_none, cib_process_default
},
{
PCMK__CIB_REQUEST_QUERY, FALSE, FALSE, FALSE,
cib_prepare_none, cib_cleanup_query, cib_process_query
},
{
CIB_OP_MODIFY, TRUE, TRUE, TRUE,
cib_prepare_data, cib_cleanup_data, cib_process_modify
},
{
CIB_OP_APPLY_DIFF, TRUE, TRUE, TRUE,
cib_prepare_diff, cib_cleanup_data, cib_server_process_diff
},
{
CIB_OP_REPLACE, TRUE, TRUE, TRUE,
cib_prepare_data, cib_cleanup_data, cib_process_replace_svr
},
{
- CIB_OP_CREATE, TRUE, TRUE, TRUE,
+ PCMK__CIB_REQUEST_CREATE, TRUE, TRUE, TRUE,
cib_prepare_data, cib_cleanup_data, cib_process_create
},
{
CIB_OP_DELETE, TRUE, TRUE, TRUE,
cib_prepare_data, cib_cleanup_data, cib_process_delete
},
{
PCMK__CIB_REQUEST_SYNC_TO_ALL, FALSE, TRUE, FALSE,
cib_prepare_sync, cib_cleanup_none, cib_process_sync
},
{
PCMK__CIB_REQUEST_BUMP, TRUE, TRUE, TRUE,
cib_prepare_none, cib_cleanup_output, cib_process_bump
},
{
CIB_OP_ERASE, TRUE, TRUE, TRUE,
cib_prepare_none, cib_cleanup_output, cib_process_erase
},
{
CRM_OP_NOOP, FALSE, FALSE, FALSE,
cib_prepare_none, cib_cleanup_none, cib_process_default
},
{
CIB_OP_DELETE_ALT, TRUE, TRUE, TRUE,
cib_prepare_data, cib_cleanup_data, cib_process_delete_absolute
},
{
CIB_OP_UPGRADE, TRUE, TRUE, TRUE,
cib_prepare_none, cib_cleanup_output, cib_process_upgrade_server
},
{
PCMK__CIB_REQUEST_SECONDARY, FALSE, TRUE, FALSE,
cib_prepare_none, cib_cleanup_none, cib_process_readwrite
},
{
PCMK__CIB_REQUEST_ALL_SECONDARY, FALSE, TRUE, FALSE,
cib_prepare_none, cib_cleanup_none, cib_process_readwrite
},
{
PCMK__CIB_REQUEST_SYNC_TO_ONE, FALSE, TRUE, FALSE,
cib_prepare_sync, cib_cleanup_none, cib_process_sync_one
},
{
PCMK__CIB_REQUEST_PRIMARY, TRUE, TRUE, FALSE,
cib_prepare_data, cib_cleanup_data, cib_process_readwrite
},
{
PCMK__CIB_REQUEST_IS_PRIMARY, FALSE, TRUE, FALSE,
cib_prepare_none, cib_cleanup_none, cib_process_readwrite
},
{
"cib_shutdown_req", FALSE, TRUE, FALSE,
cib_prepare_sync, cib_cleanup_none, cib_process_shutdown_req
},
{
CRM_OP_PING, FALSE, FALSE, FALSE,
cib_prepare_none, cib_cleanup_output, cib_process_ping
},
};
int
cib_get_operation_id(const char *op, int *operation)
{
static GHashTable *operation_hash = NULL;
if (operation_hash == NULL) {
int lpc = 0;
int max_msg_types = PCMK__NELEM(cib_server_ops);
operation_hash = pcmk__strkey_table(NULL, free);
for (lpc = 1; lpc < max_msg_types; lpc++) {
int *value = malloc(sizeof(int));
if(value) {
*value = lpc;
g_hash_table_insert(operation_hash, (gpointer) cib_server_ops[lpc].operation, value);
}
}
}
if (op != NULL) {
int *value = g_hash_table_lookup(operation_hash, op);
if (value) {
*operation = *value;
return pcmk_ok;
}
}
crm_err("Operation %s is not valid", op);
*operation = -1;
return -EINVAL;
}
xmlNode *
cib_msg_copy(xmlNode * msg, gboolean with_data)
{
int lpc = 0;
const char *field = NULL;
const char *value = NULL;
xmlNode *value_struct = NULL;
static const char *field_list[] = {
F_XML_TAGNAME,
F_TYPE,
F_CIB_CLIENTID,
F_CIB_CALLOPTS,
F_CIB_CALLID,
F_CIB_OPERATION,
F_CIB_ISREPLY,
F_CIB_SECTION,
F_CIB_HOST,
F_CIB_RC,
F_CIB_DELEGATED,
F_CIB_OBJID,
F_CIB_OBJTYPE,
F_CIB_EXISTING,
F_CIB_SEENCOUNT,
F_CIB_TIMEOUT,
F_CIB_CALLBACK_TOKEN,
F_CIB_GLOBAL_UPDATE,
F_CIB_CLIENTNAME,
F_CIB_USER,
F_CIB_NOTIFY_TYPE,
F_CIB_NOTIFY_ACTIVATE
};
static const char *data_list[] = {
F_CIB_CALLDATA,
F_CIB_UPDATE,
F_CIB_UPDATE_RESULT
};
xmlNode *copy = create_xml_node(NULL, "copy");
CRM_ASSERT(copy != NULL);
for (lpc = 0; lpc < PCMK__NELEM(field_list); lpc++) {
field = field_list[lpc];
value = crm_element_value(msg, field);
if (value != NULL) {
crm_xml_add(copy, field, value);
}
}
for (lpc = 0; with_data && lpc < PCMK__NELEM(data_list); lpc++) {
field = data_list[lpc];
value_struct = get_message_xml(msg, field);
if (value_struct != NULL) {
add_message_xml(copy, field, value_struct);
}
}
return copy;
}
cib_op_t *
cib_op_func(int call_type)
{
return &(cib_server_ops[call_type].fn);
}
gboolean
cib_op_modifies(int call_type)
{
return cib_server_ops[call_type].modifies_cib;
}
int
cib_op_can_run(int call_type, int call_options, gboolean privileged, gboolean global_update)
{
if (privileged == FALSE && cib_server_ops[call_type].needs_privileges) {
/* abort */
return -EACCES;
}
#if 0
if (rc == pcmk_ok
&& stand_alone == FALSE
&& global_update == FALSE
&& (call_options & cib_quorum_override) == 0 && cib_server_ops[call_type].needs_quorum) {
return -pcmk_err_no_quorum;
}
#endif
return pcmk_ok;
}
int
cib_op_prepare(int call_type, xmlNode * request, xmlNode ** input, const char **section)
{
crm_trace("Prepare %d", call_type);
return cib_server_ops[call_type].prepare(request, input, section);
}
int
cib_op_cleanup(int call_type, int options, xmlNode ** input, xmlNode ** output)
{
crm_trace("Cleanup %d", call_type);
return cib_server_ops[call_type].cleanup(options, input, output);
}
diff --git a/include/crm/cib/internal.h b/include/crm/cib/internal.h
index 1f687f49bc..c54492030c 100644
--- a/include/crm/cib/internal.h
+++ b/include/crm/cib/internal.h
@@ -1,261 +1,261 @@
/*
* Copyright 2004-2022 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU Lesser General Public License
* version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
*/
#ifndef CIB_INTERNAL__H
# define CIB_INTERNAL__H
# include <crm/cib.h>
# include <crm/common/ipc_internal.h>
# include <crm/common/output_internal.h>
// Request types for CIB manager IPC/CPG
#define PCMK__CIB_REQUEST_SECONDARY "cib_slave"
#define PCMK__CIB_REQUEST_ALL_SECONDARY "cib_slave_all"
#define PCMK__CIB_REQUEST_PRIMARY "cib_master"
#define PCMK__CIB_REQUEST_SYNC_TO_ALL "cib_sync"
#define PCMK__CIB_REQUEST_SYNC_TO_ONE "cib_sync_one"
#define PCMK__CIB_REQUEST_IS_PRIMARY "cib_ismaster"
#define PCMK__CIB_REQUEST_BUMP "cib_bump"
#define PCMK__CIB_REQUEST_QUERY "cib_query"
-# define CIB_OP_CREATE "cib_create"
+#define PCMK__CIB_REQUEST_CREATE "cib_create"
# define CIB_OP_MODIFY "cib_modify"
# define CIB_OP_DELETE "cib_delete"
# define CIB_OP_ERASE "cib_erase"
# define CIB_OP_REPLACE "cib_replace"
# define CIB_OP_APPLY_DIFF "cib_apply_diff"
# define CIB_OP_UPGRADE "cib_upgrade"
# define CIB_OP_DELETE_ALT "cib_delete_alt"
# define F_CIB_CLIENTID "cib_clientid"
# define F_CIB_CALLOPTS "cib_callopt"
# define F_CIB_CALLID "cib_callid"
# define F_CIB_CALLDATA "cib_calldata"
# define F_CIB_OPERATION "cib_op"
# define F_CIB_ISREPLY "cib_isreplyto"
# define F_CIB_SECTION "cib_section"
# define F_CIB_HOST "cib_host"
# define F_CIB_RC "cib_rc"
# define F_CIB_UPGRADE_RC "cib_upgrade_rc"
# define F_CIB_DELEGATED "cib_delegated_from"
# define F_CIB_OBJID "cib_object"
# define F_CIB_OBJTYPE "cib_object_type"
# define F_CIB_EXISTING "cib_existing_object"
# define F_CIB_SEENCOUNT "cib_seen"
# define F_CIB_TIMEOUT "cib_timeout"
# define F_CIB_UPDATE "cib_update"
# define F_CIB_CALLBACK_TOKEN "cib_async_id"
# define F_CIB_GLOBAL_UPDATE "cib_update"
# define F_CIB_UPDATE_RESULT "cib_update_result"
# define F_CIB_CLIENTNAME "cib_clientname"
# define F_CIB_NOTIFY_TYPE "cib_notify_type"
# define F_CIB_NOTIFY_ACTIVATE "cib_notify_activate"
# define F_CIB_UPDATE_DIFF "cib_update_diff"
# define F_CIB_USER "cib_user"
# define F_CIB_LOCAL_NOTIFY_ID "cib_local_notify_id"
# define F_CIB_PING_ID "cib_ping_id"
# define F_CIB_SCHEMA_MAX "cib_schema_max"
# define F_CIB_CHANGE_SECTION "cib_change_section"
# define T_CIB "cib"
# define T_CIB_NOTIFY "cib_notify"
/* notify sub-types */
# define T_CIB_PRE_NOTIFY "cib_pre_notify"
# define T_CIB_POST_NOTIFY "cib_post_notify"
# define T_CIB_UPDATE_CONFIRM "cib_update_confirmation"
# define T_CIB_REPLACE_NOTIFY "cib_refresh_notify"
enum cib_change_section_info {
cib_change_section_none = 0x00000000,
cib_change_section_nodes = 0x00000001,
cib_change_section_alerts = 0x00000002,
cib_change_section_status = 0x00000004
};
gboolean cib_diff_version_details(xmlNode * diff, int *admin_epoch, int *epoch, int *updates,
int *_admin_epoch, int *_epoch, int *_updates);
gboolean cib_read_config(GHashTable * options, xmlNode * current_cib);
void verify_cib_options(GHashTable * options);
gboolean cib_internal_config_changed(xmlNode * diff);
typedef struct cib_notify_client_s {
const char *event;
const char *obj_id; /* implement one day */
const char *obj_type; /* implement one day */
void (*callback) (const char *event, xmlNode * msg);
} cib_notify_client_t;
typedef struct cib_callback_client_s {
void (*callback) (xmlNode *, int, int, xmlNode *, void *);
const char *id;
void *user_data;
gboolean only_success;
struct timer_rec_s *timer;
void (*free_func)(void *);
} cib_callback_client_t;
struct timer_rec_s {
int call_id;
int timeout;
guint ref;
cib_t *cib;
};
#define cib__set_call_options(cib_call_opts, call_for, flags_to_set) do { \
cib_call_opts = pcmk__set_flags_as(__func__, __LINE__, \
LOG_TRACE, "CIB call", (call_for), (cib_call_opts), \
(flags_to_set), #flags_to_set); \
} while (0)
#define cib__clear_call_options(cib_call_opts, call_for, flags_to_clear) do { \
cib_call_opts = pcmk__clear_flags_as(__func__, __LINE__, \
LOG_TRACE, "CIB call", (call_for), (cib_call_opts), \
(flags_to_clear), #flags_to_clear); \
} while (0)
typedef int (*cib_op_t) (const char *, int, const char *, xmlNode *,
xmlNode *, xmlNode *, xmlNode **, xmlNode **);
cib_t *cib_new_variant(void);
int cib_perform_op(const char *op, int call_options, cib_op_t * fn, gboolean is_query,
const char *section, xmlNode * req, xmlNode * input,
gboolean manage_counters, gboolean * config_changed,
xmlNode * current_cib, xmlNode ** result_cib, xmlNode ** diff,
xmlNode ** output);
xmlNode *cib_create_op(int call_id, const char *token, const char *op, const char *host,
const char *section, xmlNode * data, int call_options,
const char *user_name);
void cib_native_callback(cib_t * cib, xmlNode * msg, int call_id, int rc);
void cib_native_notify(gpointer data, gpointer user_data);
int cib_native_register_notification(cib_t * cib, const char *callback, int enabled);
gboolean cib_client_register_callback(cib_t * cib, int call_id, int timeout, gboolean only_success,
void *user_data, const char *callback_name,
void (*callback) (xmlNode *, int, int, xmlNode *, void *));
gboolean cib_client_register_callback_full(cib_t *cib, int call_id,
int timeout, gboolean only_success,
void *user_data,
const char *callback_name,
void (*callback)(xmlNode *, int, int,
xmlNode *, void *),
void (*free_func)(void *));
int cib_process_query(const char *op, int options, const char *section, xmlNode * req,
xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib,
xmlNode ** answer);
int cib_process_erase(const char *op, int options, const char *section, xmlNode * req,
xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib,
xmlNode ** answer);
int cib_process_bump(const char *op, int options, const char *section, xmlNode * req,
xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib,
xmlNode ** answer);
int cib_process_replace(const char *op, int options, const char *section, xmlNode * req,
xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib,
xmlNode ** answer);
int cib_process_create(const char *op, int options, const char *section, xmlNode * req,
xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib,
xmlNode ** answer);
int cib_process_modify(const char *op, int options, const char *section, xmlNode * req,
xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib,
xmlNode ** answer);
int cib_process_delete(const char *op, int options, const char *section, xmlNode * req,
xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib,
xmlNode ** answer);
int cib_process_diff(const char *op, int options, const char *section, xmlNode * req,
xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib,
xmlNode ** answer);
int cib_process_upgrade(const char *op, int options, const char *section, xmlNode * req,
xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib,
xmlNode ** answer);
/*!
* \internal
* \brief Query or modify a CIB
*
* \param[in] op CIB_OP_* operation to be performed
* \param[in] options Flag set of \c cib_call_options
* \param[in] section XPath to query or modify
* \param[in] req unused
* \param[in] input Portion of CIB to modify (used with
- * CIB_OP_CREATE, CIB_OP_MODIFY, and
+ * PCMK__CIB_REQUEST_CREATE, CIB_OP_MODIFY, and
* CIB_OP_REPLACE)
* \param[in] existing_cib Input CIB (used with PCMK__CIB_REQUEST_QUERY)
* \param[in,out] result_cib CIB copy to make changes in (used with
- * CIB_OP_CREATE, CIB_OP_MODIFY, CIB_OP_DELETE, and
- * CIB_OP_REPLACE)
+ * PCMK__CIB_REQUEST_CREATE, CIB_OP_MODIFY,
+ * CIB_OP_DELETE, and CIB_OP_REPLACE)
* \param[out] answer Query result (used with PCMK__CIB_REQUEST_QUERY)
*
* \return Legacy Pacemaker return code
*/
int cib_process_xpath(const char *op, int options, const char *section, xmlNode * req,
xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib,
xmlNode ** answer);
gboolean cib_config_changed(xmlNode * last, xmlNode * next, xmlNode ** diff);
gboolean update_results(xmlNode * failed, xmlNode * target, const char *operation, int return_code);
int cib_update_counter(xmlNode * xml_obj, const char *field, gboolean reset);
int cib_internal_op(cib_t * cib, const char *op, const char *host,
const char *section, xmlNode * data,
xmlNode ** output_data, int call_options, const char *user_name);
int cib_file_read_and_verify(const char *filename, const char *sigfile,
xmlNode **root);
int cib_file_write_with_digest(xmlNode *cib_root, const char *cib_dirname,
const char *cib_filename);
void cib__set_output(cib_t *cib, pcmk__output_t *out);
cib_callback_client_t* cib__lookup_id (int call_id);
/*!
* \internal
* \brief Connect to, query, and optionally disconnect from the CIB, returning
* the resulting XML object.
*
* \param[out] cib If non-NULL, a pointer to where to store the CIB
* connection. In this case, it is up to the caller to
* disconnect from the CIB when finished.
* \param[out] cib_object A pointer to where to store the XML query result.
*
* \return A standard Pacemaker return code
*/
int cib__signon_query(cib_t **cib, xmlNode **cib_object);
int cib__clean_up_connection(cib_t **cib);
int cib__update_node_attr(pcmk__output_t *out, cib_t *cib, int call_options,
const char *section, const char *node_uuid, const char *set_type,
const char *set_name, const char *attr_id, const char *attr_name,
const char *attr_value, const char *user_name,
const char *node_type);
int cib__get_node_attrs(pcmk__output_t *out, cib_t *cib, const char *section,
const char *node_uuid, const char *set_type, const char *set_name,
const char *attr_id, const char *attr_name, const char *user_name,
xmlNode **result);
int cib__delete_node_attr(pcmk__output_t *out, cib_t *cib, int options,
const char *section, const char *node_uuid, const char *set_type,
const char *set_name, const char *attr_id, const char *attr_name,
const char *attr_value, const char *user_name);
#endif
diff --git a/lib/cib/cib_client.c b/lib/cib/cib_client.c
index 5bfc1c23db..beaf7a3815 100644
--- a/lib/cib/cib_client.c
+++ b/lib/cib/cib_client.c
@@ -1,711 +1,712 @@
/*
* Copyright 2004-2022 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 <pwd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <glib.h>
#include <crm/crm.h>
#include <crm/cib/internal.h>
#include <crm/msg_xml.h>
#include <crm/common/xml.h>
static GHashTable *cib_op_callback_table = NULL;
int cib_client_set_op_callback(cib_t * cib, void (*callback) (const xmlNode * msg, int call_id,
int rc, xmlNode * output));
int cib_client_add_notify_callback(cib_t * cib, const char *event,
void (*callback) (const char *event, xmlNode * msg));
int cib_client_del_notify_callback(cib_t * cib, const char *event,
void (*callback) (const char *event, xmlNode * msg));
gint ciblib_GCompareFunc(gconstpointer a, gconstpointer b);
#define op_common(cib) do { \
if(cib == NULL) { \
return -EINVAL; \
} else if(cib->delegate_fn == NULL) { \
return -EPROTONOSUPPORT; \
} \
} while(0)
static int
cib_client_noop(cib_t * cib, int call_options)
{
op_common(cib);
return cib_internal_op(cib, CRM_OP_NOOP, NULL, NULL, NULL, NULL, call_options, NULL);
}
static int
cib_client_ping(cib_t * cib, xmlNode ** output_data, int call_options)
{
op_common(cib);
return cib_internal_op(cib, CRM_OP_PING, NULL, NULL, NULL, output_data, call_options, NULL);
}
static int
cib_client_query(cib_t * cib, const char *section, xmlNode ** output_data, int call_options)
{
return cib->cmds->query_from(cib, NULL, section, output_data, call_options);
}
static int
cib_client_query_from(cib_t * cib, const char *host, const char *section,
xmlNode ** output_data, int call_options)
{
op_common(cib);
return cib_internal_op(cib, PCMK__CIB_REQUEST_QUERY, host, section, NULL,
output_data, call_options, NULL);
}
static int
cib_client_is_master(cib_t * cib)
{
op_common(cib);
return cib_internal_op(cib, PCMK__CIB_REQUEST_IS_PRIMARY, NULL, NULL, NULL,
NULL, cib_scope_local|cib_sync_call, NULL);
}
static int
cib_client_set_slave(cib_t * cib, int call_options)
{
op_common(cib);
return cib_internal_op(cib, PCMK__CIB_REQUEST_SECONDARY, NULL, NULL, NULL,
NULL, call_options, NULL);
}
static int
cib_client_set_slave_all(cib_t * cib, int call_options)
{
return -EPROTONOSUPPORT;
}
static int
cib_client_set_master(cib_t * cib, int call_options)
{
op_common(cib);
crm_trace("Adding cib_scope_local to options");
return cib_internal_op(cib, PCMK__CIB_REQUEST_PRIMARY, NULL, NULL, NULL,
NULL, call_options|cib_scope_local, NULL);
}
static int
cib_client_bump_epoch(cib_t * cib, int call_options)
{
op_common(cib);
return cib_internal_op(cib, PCMK__CIB_REQUEST_BUMP, NULL, NULL, NULL, NULL,
call_options, NULL);
}
static int
cib_client_upgrade(cib_t * cib, int call_options)
{
op_common(cib);
return cib_internal_op(cib, CIB_OP_UPGRADE, NULL, NULL, NULL, NULL, call_options, NULL);
}
static int
cib_client_sync(cib_t * cib, const char *section, int call_options)
{
return cib->cmds->sync_from(cib, NULL, section, call_options);
}
static int
cib_client_sync_from(cib_t * cib, const char *host, const char *section, int call_options)
{
op_common(cib);
return cib_internal_op(cib, PCMK__CIB_REQUEST_SYNC_TO_ALL, host, section,
NULL, NULL, call_options, NULL);
}
static int
cib_client_create(cib_t * cib, const char *section, xmlNode * data, int call_options)
{
op_common(cib);
- return cib_internal_op(cib, CIB_OP_CREATE, NULL, section, data, NULL, call_options, NULL);
+ return cib_internal_op(cib, PCMK__CIB_REQUEST_CREATE, NULL, section, data,
+ NULL, call_options, NULL);
}
static int
cib_client_modify(cib_t * cib, const char *section, xmlNode * data, int call_options)
{
op_common(cib);
return cib_internal_op(cib, CIB_OP_MODIFY, NULL, section, data, NULL, call_options, NULL);
}
static int
cib_client_update(cib_t * cib, const char *section, xmlNode * data, int call_options)
{
op_common(cib);
return cib_internal_op(cib, CIB_OP_MODIFY, NULL, section, data, NULL, call_options, NULL);
}
static int
cib_client_replace(cib_t * cib, const char *section, xmlNode * data, int call_options)
{
op_common(cib);
return cib_internal_op(cib, CIB_OP_REPLACE, NULL, section, data, NULL, call_options, NULL);
}
static int
cib_client_delete(cib_t * cib, const char *section, xmlNode * data, int call_options)
{
op_common(cib);
return cib_internal_op(cib, CIB_OP_DELETE, NULL, section, data, NULL, call_options, NULL);
}
static int
cib_client_delete_absolute(cib_t * cib, const char *section, xmlNode * data, int call_options)
{
op_common(cib);
return cib_internal_op(cib, CIB_OP_DELETE_ALT, NULL, section, data, NULL, call_options, NULL);
}
static int
cib_client_erase(cib_t * cib, xmlNode ** output_data, int call_options)
{
op_common(cib);
return cib_internal_op(cib, CIB_OP_ERASE, NULL, NULL, NULL, output_data, call_options, NULL);
}
static void
cib_destroy_op_callback(gpointer data)
{
cib_callback_client_t *blob = data;
if (blob->timer && blob->timer->ref > 0) {
g_source_remove(blob->timer->ref);
}
free(blob->timer);
if (blob->user_data && blob->free_func) {
blob->free_func(blob->user_data);
}
free(blob);
}
static void
destroy_op_callback_table(void)
{
if (cib_op_callback_table != NULL) {
g_hash_table_destroy(cib_op_callback_table);
cib_op_callback_table = NULL;
}
}
char *
get_shadow_file(const char *suffix)
{
char *cib_home = NULL;
char *fullname = NULL;
char *name = crm_strdup_printf("shadow.%s", suffix);
const char *dir = getenv("CIB_shadow_dir");
if (dir == NULL) {
uid_t uid = geteuid();
struct passwd *pwent = getpwuid(uid);
const char *user = NULL;
if (pwent) {
user = pwent->pw_name;
} else {
user = getenv("USER");
crm_perror(LOG_ERR,
"Assuming %s because cannot get user details for user ID %d",
(user? user : "unprivileged user"), uid);
}
if (pcmk__strcase_any_of(user, "root", CRM_DAEMON_USER, NULL)) {
dir = CRM_CONFIG_DIR;
} else {
const char *home = NULL;
if ((home = getenv("HOME")) == NULL) {
if (pwent) {
home = pwent->pw_dir;
}
}
dir = pcmk__get_tmpdir();
if (home && home[0] == '/') {
int rc = 0;
cib_home = crm_strdup_printf("%s/.cib", home);
rc = mkdir(cib_home, 0700);
if (rc < 0 && errno != EEXIST) {
crm_perror(LOG_ERR, "Couldn't create user-specific shadow directory: %s",
cib_home);
errno = 0;
} else {
dir = cib_home;
}
}
}
}
fullname = crm_strdup_printf("%s/%s", dir, name);
free(cib_home);
free(name);
return fullname;
}
cib_t *
cib_shadow_new(const char *shadow)
{
cib_t *new_cib = NULL;
char *shadow_file = NULL;
CRM_CHECK(shadow != NULL, return NULL);
shadow_file = get_shadow_file(shadow);
new_cib = cib_file_new(shadow_file);
free(shadow_file);
return new_cib;
}
cib_t *
cib_new_no_shadow(void)
{
unsetenv("CIB_shadow");
return cib_new();
}
cib_t *
cib_new(void)
{
const char *value = getenv("CIB_shadow");
int port;
if (value && value[0] != 0) {
return cib_shadow_new(value);
}
value = getenv("CIB_file");
if (value) {
return cib_file_new(value);
}
value = getenv("CIB_port");
if (value) {
gboolean encrypted = TRUE;
const char *server = getenv("CIB_server");
const char *user = getenv("CIB_user");
const char *pass = getenv("CIB_passwd");
/* We don't ensure port is valid (>= 0) because cib_new() currently
* can't return NULL in practice, and introducing a NULL return here
* could cause core dumps that would previously just cause signon()
* failures.
*/
pcmk__scan_port(value, &port);
value = getenv("CIB_encrypted");
if (value && crm_is_true(value) == FALSE) {
crm_info("Disabling TLS");
encrypted = FALSE;
}
if (user == NULL) {
user = CRM_DAEMON_USER;
crm_info("Defaulting to user: %s", user);
}
if (server == NULL) {
server = "localhost";
crm_info("Defaulting to localhost");
}
return cib_remote_new(server, user, pass, port, encrypted);
}
return cib_native_new();
}
/*!
* \internal
* \brief Create a generic CIB connection instance
*
* \return Newly allocated and initialized cib_t instance
*
* \note This is called by each variant's cib_*_new() function before setting
* variant-specific values.
*/
cib_t *
cib_new_variant(void)
{
cib_t *new_cib = NULL;
new_cib = calloc(1, sizeof(cib_t));
if (new_cib == NULL) {
return NULL;
}
remove_cib_op_callback(0, TRUE); /* remove all */
new_cib->call_id = 1;
new_cib->variant = cib_undefined;
new_cib->type = cib_no_connection;
new_cib->state = cib_disconnected;
new_cib->op_callback = NULL;
new_cib->variant_opaque = NULL;
new_cib->notify_list = NULL;
/* the rest will get filled in by the variant constructor */
new_cib->cmds = calloc(1, sizeof(cib_api_operations_t));
if (new_cib->cmds == NULL) {
free(new_cib);
return NULL;
}
new_cib->cmds->set_op_callback = cib_client_set_op_callback;
new_cib->cmds->add_notify_callback = cib_client_add_notify_callback;
new_cib->cmds->del_notify_callback = cib_client_del_notify_callback;
new_cib->cmds->register_callback = cib_client_register_callback;
new_cib->cmds->register_callback_full = cib_client_register_callback_full;
new_cib->cmds->noop = cib_client_noop;
new_cib->cmds->ping = cib_client_ping;
new_cib->cmds->query = cib_client_query;
new_cib->cmds->sync = cib_client_sync;
new_cib->cmds->query_from = cib_client_query_from;
new_cib->cmds->sync_from = cib_client_sync_from;
new_cib->cmds->is_master = cib_client_is_master;
new_cib->cmds->set_master = cib_client_set_master;
new_cib->cmds->set_slave = cib_client_set_slave;
new_cib->cmds->set_slave_all = cib_client_set_slave_all;
new_cib->cmds->upgrade = cib_client_upgrade;
new_cib->cmds->bump_epoch = cib_client_bump_epoch;
new_cib->cmds->create = cib_client_create;
new_cib->cmds->modify = cib_client_modify;
new_cib->cmds->update = cib_client_update;
new_cib->cmds->replace = cib_client_replace;
new_cib->cmds->remove = cib_client_delete;
new_cib->cmds->erase = cib_client_erase;
new_cib->cmds->delete_absolute = cib_client_delete_absolute;
return new_cib;
}
void
cib_free_notify(cib_t *cib)
{
if (cib) {
GList *list = cib->notify_list;
while (list != NULL) {
cib_notify_client_t *client = g_list_nth_data(list, 0);
list = g_list_remove(list, client);
free(client);
}
cib->notify_list = NULL;
}
}
/*!
* \brief Free all callbacks for a CIB connection
*
* \param[in] cib CIB connection to clean up
*/
void
cib_free_callbacks(cib_t *cib)
{
cib_free_notify(cib);
destroy_op_callback_table();
}
/*!
* \brief Free all memory used by CIB connection
*
* \param[in] cib CIB connection to delete
*/
void
cib_delete(cib_t *cib)
{
cib_free_callbacks(cib);
if (cib) {
cib->cmds->free(cib);
}
}
int
cib_client_set_op_callback(cib_t * cib, void (*callback) (const xmlNode * msg, int call_id,
int rc, xmlNode * output))
{
if (callback == NULL) {
crm_info("Un-Setting operation callback");
} else {
crm_trace("Setting operation callback");
}
cib->op_callback = callback;
return pcmk_ok;
}
int
cib_client_add_notify_callback(cib_t * cib, const char *event,
void (*callback) (const char *event, xmlNode * msg))
{
GList *list_item = NULL;
cib_notify_client_t *new_client = NULL;
if (cib->variant != cib_native && cib->variant != cib_remote) {
return -EPROTONOSUPPORT;
}
crm_trace("Adding callback for %s events (%d)", event, g_list_length(cib->notify_list));
new_client = calloc(1, sizeof(cib_notify_client_t));
new_client->event = event;
new_client->callback = callback;
list_item = g_list_find_custom(cib->notify_list, new_client, ciblib_GCompareFunc);
if (list_item != NULL) {
crm_warn("Callback already present");
free(new_client);
return -EINVAL;
} else {
cib->notify_list = g_list_append(cib->notify_list, new_client);
cib->cmds->register_notification(cib, event, 1);
crm_trace("Callback added (%d)", g_list_length(cib->notify_list));
}
return pcmk_ok;
}
static int
get_notify_list_event_count(cib_t * cib, const char *event)
{
GList *l = NULL;
int count = 0;
for (l = g_list_first(cib->notify_list); l; l = g_list_next(l)) {
cib_notify_client_t *client = (cib_notify_client_t *)l->data;
if (strcmp(client->event, event) == 0) {
count++;
}
}
crm_trace("event(%s) count : %d", event, count);
return count;
}
int
cib_client_del_notify_callback(cib_t * cib, const char *event,
void (*callback) (const char *event, xmlNode * msg))
{
GList *list_item = NULL;
cib_notify_client_t *new_client = NULL;
if (cib->variant != cib_native && cib->variant != cib_remote) {
return -EPROTONOSUPPORT;
}
if (get_notify_list_event_count(cib, event) == 0) {
crm_debug("The callback of the event does not exist(%s)", event);
return pcmk_ok;
}
crm_debug("Removing callback for %s events", event);
new_client = calloc(1, sizeof(cib_notify_client_t));
new_client->event = event;
new_client->callback = callback;
list_item = g_list_find_custom(cib->notify_list, new_client, ciblib_GCompareFunc);
if (list_item != NULL) {
cib_notify_client_t *list_client = list_item->data;
cib->notify_list = g_list_remove(cib->notify_list, list_client);
free(list_client);
crm_trace("Removed callback");
} else {
crm_trace("Callback not present");
}
if (get_notify_list_event_count(cib, event) == 0) {
/* When there is not the registration of the event, the processing turns off a notice. */
cib->cmds->register_notification(cib, event, 0);
}
free(new_client);
return pcmk_ok;
}
gint
ciblib_GCompareFunc(gconstpointer a, gconstpointer b)
{
int rc = 0;
const cib_notify_client_t *a_client = a;
const cib_notify_client_t *b_client = b;
CRM_CHECK(a_client->event != NULL && b_client->event != NULL, return 0);
rc = strcmp(a_client->event, b_client->event);
if (rc == 0) {
if (a_client->callback == b_client->callback) {
return 0;
} else if (((long)a_client->callback) < ((long)b_client->callback)) {
crm_trace("callbacks for %s are not equal: %p < %p",
a_client->event, a_client->callback, b_client->callback);
return -1;
}
crm_trace("callbacks for %s are not equal: %p > %p",
a_client->event, a_client->callback, b_client->callback);
return 1;
}
return rc;
}
static gboolean
cib_async_timeout_handler(gpointer data)
{
struct timer_rec_s *timer = data;
crm_debug("Async call %d timed out after %ds", timer->call_id, timer->timeout);
cib_native_callback(timer->cib, NULL, timer->call_id, -ETIME);
/* Always return TRUE, never remove the handler
* We do that in remove_cib_op_callback()
*/
return TRUE;
}
gboolean
cib_client_register_callback(cib_t * cib, int call_id, int timeout, gboolean only_success,
void *user_data, const char *callback_name,
void (*callback) (xmlNode *, int, int, xmlNode *, void *))
{
return cib_client_register_callback_full(cib, call_id, timeout,
only_success, user_data,
callback_name, callback, NULL);
}
gboolean
cib_client_register_callback_full(cib_t *cib, int call_id, int timeout,
gboolean only_success, void *user_data,
const char *callback_name,
void (*callback)(xmlNode *, int, int,
xmlNode *, void *),
void (*free_func)(void *))
{
cib_callback_client_t *blob = NULL;
if (call_id < 0) {
if (only_success == FALSE) {
callback(NULL, call_id, call_id, NULL, user_data);
} else {
crm_warn("CIB call failed: %s", pcmk_strerror(call_id));
}
if (user_data && free_func) {
free_func(user_data);
}
return FALSE;
}
blob = calloc(1, sizeof(cib_callback_client_t));
blob->id = callback_name;
blob->only_success = only_success;
blob->user_data = user_data;
blob->callback = callback;
blob->free_func = free_func;
if (timeout > 0) {
struct timer_rec_s *async_timer = NULL;
async_timer = calloc(1, sizeof(struct timer_rec_s));
blob->timer = async_timer;
async_timer->cib = cib;
async_timer->call_id = call_id;
async_timer->timeout = timeout * 1000;
async_timer->ref =
g_timeout_add(async_timer->timeout, cib_async_timeout_handler, async_timer);
}
crm_trace("Adding callback %s for call %d", callback_name, call_id);
pcmk__intkey_table_insert(cib_op_callback_table, call_id, blob);
return TRUE;
}
void
remove_cib_op_callback(int call_id, gboolean all_callbacks)
{
if (all_callbacks) {
destroy_op_callback_table();
cib_op_callback_table = pcmk__intkey_table(cib_destroy_op_callback);
} else {
pcmk__intkey_table_remove(cib_op_callback_table, call_id);
}
}
int
num_cib_op_callbacks(void)
{
if (cib_op_callback_table == NULL) {
return 0;
}
return g_hash_table_size(cib_op_callback_table);
}
static void
cib_dump_pending_op(gpointer key, gpointer value, gpointer user_data)
{
int call = GPOINTER_TO_INT(key);
cib_callback_client_t *blob = value;
crm_debug("Call %d (%s): pending", call, pcmk__s(blob->id, "without ID"));
}
void
cib_dump_pending_callbacks(void)
{
if (cib_op_callback_table == NULL) {
return;
}
return g_hash_table_foreach(cib_op_callback_table, cib_dump_pending_op, NULL);
}
cib_callback_client_t*
cib__lookup_id (int call_id)
{
return pcmk__intkey_table_lookup(cib_op_callback_table, call_id);
}
diff --git a/lib/cib/cib_file.c b/lib/cib/cib_file.c
index 13c27a5fd4..8e9e6f498c 100644
--- a/lib/cib/cib_file.c
+++ b/lib/cib/cib_file.c
@@ -1,897 +1,897 @@
/*
* Original copyright 2004 International Business Machines
* Later changes copyright 2008-2022 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU Lesser General Public License
* version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
*/
#include <crm_internal.h>
#include <unistd.h>
#include <limits.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <pwd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <glib.h>
#include <crm/crm.h>
#include <crm/cib/internal.h>
#include <crm/msg_xml.h>
#include <crm/common/ipc.h>
#include <crm/common/xml.h>
#include <crm/common/xml_internal.h>
enum cib_file_flags {
cib_file_flag_dirty = (1 << 0),
cib_file_flag_live = (1 << 1),
};
typedef struct cib_file_opaque_s {
uint32_t flags; // Group of enum cib_file_flags
char *filename;
} cib_file_opaque_t;
#define cib_set_file_flags(cibfile, flags_to_set) do { \
(cibfile)->flags = pcmk__set_flags_as(__func__, __LINE__, \
LOG_TRACE, "CIB file", \
cibfile->filename, \
(cibfile)->flags, \
(flags_to_set), \
#flags_to_set); \
} while (0)
#define cib_clear_file_flags(cibfile, flags_to_clear) do { \
(cibfile)->flags = pcmk__clear_flags_as(__func__, __LINE__, \
LOG_TRACE, "CIB file", \
cibfile->filename, \
(cibfile)->flags, \
(flags_to_clear), \
#flags_to_clear); \
} while (0)
int cib_file_perform_op(cib_t * cib, const char *op, const char *host, const char *section,
xmlNode * data, xmlNode ** output_data, int call_options);
int cib_file_perform_op_delegate(cib_t * cib, const char *op, const char *host, const char *section,
xmlNode * data, xmlNode ** output_data, int call_options,
const char *user_name);
int cib_file_signon(cib_t * cib, const char *name, enum cib_conn_type type);
int cib_file_signoff(cib_t * cib);
int cib_file_free(cib_t * cib);
static int
cib_file_inputfd(cib_t * cib)
{
return -EPROTONOSUPPORT;
}
static int
cib_file_set_connection_dnotify(cib_t * cib, void (*dnotify) (gpointer user_data))
{
return -EPROTONOSUPPORT;
}
static int
cib_file_register_notification(cib_t * cib, const char *callback, int enabled)
{
return -EPROTONOSUPPORT;
}
/*!
* \internal
* \brief Compare the calculated digest of an XML tree against a signature file
*
* \param[in] root Root of XML tree to compare
* \param[in] sigfile Name of signature file containing digest to compare
*
* \return TRUE if digests match or signature file does not exist, else FALSE
*/
static gboolean
cib_file_verify_digest(xmlNode *root, const char *sigfile)
{
gboolean passed = FALSE;
char *expected;
int rc = pcmk__file_contents(sigfile, &expected);
switch (rc) {
case pcmk_rc_ok:
if (expected == NULL) {
crm_err("On-disk digest at %s is empty", sigfile);
return FALSE;
}
break;
case ENOENT:
crm_warn("No on-disk digest present at %s", sigfile);
return TRUE;
default:
crm_err("Could not read on-disk digest from %s: %s",
sigfile, pcmk_rc_str(rc));
return FALSE;
}
passed = pcmk__verify_digest(root, expected);
free(expected);
return passed;
}
/*!
* \internal
* \brief Read an XML tree from a file and verify its digest
*
* \param[in] filename Name of XML file to read
* \param[in] sigfile Name of signature file containing digest to compare
* \param[in] root If non-NULL, will be set to pointer to parsed XML tree
*
* \return 0 if file was successfully read, parsed and verified, otherwise:
* -errno on stat() failure,
* -pcmk_err_cib_corrupt if file size is 0 or XML is not parseable, or
* -pcmk_err_cib_modified if digests do not match
* \note If root is non-NULL, it is the caller's responsibility to free *root on
* successful return.
*/
int
cib_file_read_and_verify(const char *filename, const char *sigfile, xmlNode **root)
{
int s_res;
struct stat buf;
char *local_sigfile = NULL;
xmlNode *local_root = NULL;
CRM_ASSERT(filename != NULL);
if (root) {
*root = NULL;
}
/* Verify that file exists and its size is nonzero */
s_res = stat(filename, &buf);
if (s_res < 0) {
crm_perror(LOG_WARNING, "Could not verify cluster configuration file %s", filename);
return -errno;
} else if (buf.st_size == 0) {
crm_warn("Cluster configuration file %s is corrupt (size is zero)", filename);
return -pcmk_err_cib_corrupt;
}
/* Parse XML */
local_root = filename2xml(filename);
if (local_root == NULL) {
crm_warn("Cluster configuration file %s is corrupt (unparseable as XML)", filename);
return -pcmk_err_cib_corrupt;
}
/* If sigfile is not specified, use original file name plus .sig */
if (sigfile == NULL) {
sigfile = local_sigfile = crm_strdup_printf("%s.sig", filename);
}
/* Verify that digests match */
if (cib_file_verify_digest(local_root, sigfile) == FALSE) {
free(local_sigfile);
free_xml(local_root);
return -pcmk_err_cib_modified;
}
free(local_sigfile);
if (root) {
*root = local_root;
} else {
free_xml(local_root);
}
return pcmk_ok;
}
#define CIB_SERIES "cib"
#define CIB_SERIES_MAX 100
#define CIB_SERIES_BZIP FALSE /* Must be false because archived copies are
created with hard links
*/
#define CIB_LIVE_NAME CIB_SERIES ".xml"
/*!
* \internal
* \brief Check whether a file is the live CIB
*
* \param[in] filename Name of file to check
*
* \return TRUE if file exists and its real path is same as live CIB's
*/
static gboolean
cib_file_is_live(const char *filename)
{
gboolean same = FALSE;
if (filename != NULL) {
// Canonicalize file names for true comparison
char *real_filename = NULL;
if (pcmk__real_path(filename, &real_filename) == pcmk_rc_ok) {
char *real_livename = NULL;
if (pcmk__real_path(CRM_CONFIG_DIR "/" CIB_LIVE_NAME,
&real_livename) == pcmk_rc_ok) {
same = !strcmp(real_filename, real_livename);
free(real_livename);
}
free(real_filename);
}
}
return same;
}
/* cib_file_backup() and cib_file_write_with_digest() need to chown the
* written files only in limited circumstances, so these variables allow
* that to be indicated without affecting external callers
*/
static uid_t cib_file_owner = 0;
static uid_t cib_file_group = 0;
static gboolean cib_do_chown = FALSE;
/*!
* \internal
* \brief Back up a CIB
*
* \param[in] cib_dirname Directory containing CIB file and backups
* \param[in] cib_filename Name (relative to cib_dirname) of CIB file to back up
*
* \return 0 on success, -1 on error
*/
static int
cib_file_backup(const char *cib_dirname, const char *cib_filename)
{
int rc = 0;
unsigned int seq;
char *cib_path = crm_strdup_printf("%s/%s", cib_dirname, cib_filename);
char *cib_digest = crm_strdup_printf("%s.sig", cib_path);
char *backup_path;
char *backup_digest;
// Determine backup and digest file names
if (pcmk__read_series_sequence(cib_dirname, CIB_SERIES,
&seq) != pcmk_rc_ok) {
// @TODO maybe handle errors better ...
seq = 0;
}
backup_path = pcmk__series_filename(cib_dirname, CIB_SERIES, seq,
CIB_SERIES_BZIP);
backup_digest = crm_strdup_printf("%s.sig", backup_path);
/* Remove the old backups if they exist */
unlink(backup_path);
unlink(backup_digest);
/* Back up the CIB, by hard-linking it to the backup name */
if ((link(cib_path, backup_path) < 0) && (errno != ENOENT)) {
crm_perror(LOG_ERR, "Could not archive %s by linking to %s",
cib_path, backup_path);
rc = -1;
/* Back up the CIB signature similarly */
} else if ((link(cib_digest, backup_digest) < 0) && (errno != ENOENT)) {
crm_perror(LOG_ERR, "Could not archive %s by linking to %s",
cib_digest, backup_digest);
rc = -1;
/* Update the last counter and ensure everything is sync'd to media */
} else {
pcmk__write_series_sequence(cib_dirname, CIB_SERIES, ++seq,
CIB_SERIES_MAX);
if (cib_do_chown) {
int rc2;
if ((chown(backup_path, cib_file_owner, cib_file_group) < 0)
&& (errno != ENOENT)) {
crm_perror(LOG_ERR, "Could not set owner of %s", backup_path);
rc = -1;
}
if ((chown(backup_digest, cib_file_owner, cib_file_group) < 0)
&& (errno != ENOENT)) {
crm_perror(LOG_ERR, "Could not set owner of %s", backup_digest);
rc = -1;
}
rc2 = pcmk__chown_series_sequence(cib_dirname, CIB_SERIES,
cib_file_owner, cib_file_group);
if (rc2 != pcmk_rc_ok) {
crm_err("Could not set owner of sequence file in %s: %s",
cib_dirname, pcmk_rc_str(rc2));
rc = -1;
}
}
pcmk__sync_directory(cib_dirname);
crm_info("Archived previous version as %s", backup_path);
}
free(cib_path);
free(cib_digest);
free(backup_path);
free(backup_digest);
return rc;
}
/*!
* \internal
* \brief Prepare CIB XML to be written to disk
*
* Set num_updates to 0, set cib-last-written to the current timestamp,
* and strip out the status section.
*
* \param[in] root Root of CIB XML tree
*
* \return void
*/
static void
cib_file_prepare_xml(xmlNode *root)
{
xmlNode *cib_status_root = NULL;
/* Always write out with num_updates=0 and current last-written timestamp */
crm_xml_add(root, XML_ATTR_NUMUPDATES, "0");
pcmk__xe_add_last_written(root);
/* Delete status section before writing to file, because
* we discard it on startup anyway, and users get confused by it */
cib_status_root = find_xml_node(root, XML_CIB_TAG_STATUS, TRUE);
CRM_LOG_ASSERT(cib_status_root != NULL);
if (cib_status_root != NULL) {
free_xml(cib_status_root);
}
}
/*!
* \internal
* \brief Write CIB to disk, along with a signature file containing its digest
*
* \param[in] cib_root Root of XML tree to write
* \param[in] cib_dirname Directory containing CIB and signature files
* \param[in] cib_filename Name (relative to cib_dirname) of file to write
*
* \return pcmk_ok on success,
* pcmk_err_cib_modified if existing cib_filename doesn't match digest,
* pcmk_err_cib_backup if existing cib_filename couldn't be backed up,
* or pcmk_err_cib_save if new cib_filename couldn't be saved
*/
int
cib_file_write_with_digest(xmlNode *cib_root, const char *cib_dirname,
const char *cib_filename)
{
int exit_rc = pcmk_ok;
int rc, fd;
char *digest = NULL;
/* Detect CIB version for diagnostic purposes */
const char *epoch = crm_element_value(cib_root, XML_ATTR_GENERATION);
const char *admin_epoch = crm_element_value(cib_root,
XML_ATTR_GENERATION_ADMIN);
/* Determine full CIB and signature pathnames */
char *cib_path = crm_strdup_printf("%s/%s", cib_dirname, cib_filename);
char *digest_path = crm_strdup_printf("%s.sig", cib_path);
/* Create temporary file name patterns for writing out CIB and signature */
char *tmp_cib = crm_strdup_printf("%s/cib.XXXXXX", cib_dirname);
char *tmp_digest = crm_strdup_printf("%s/cib.XXXXXX", cib_dirname);
CRM_ASSERT((cib_path != NULL) && (digest_path != NULL)
&& (tmp_cib != NULL) && (tmp_digest != NULL));
/* Ensure the admin didn't modify the existing CIB underneath us */
crm_trace("Reading cluster configuration file %s", cib_path);
rc = cib_file_read_and_verify(cib_path, NULL, NULL);
if ((rc != pcmk_ok) && (rc != -ENOENT)) {
crm_err("%s was manually modified while the cluster was active!",
cib_path);
exit_rc = pcmk_err_cib_modified;
goto cleanup;
}
/* Back up the existing CIB */
if (cib_file_backup(cib_dirname, cib_filename) < 0) {
exit_rc = pcmk_err_cib_backup;
goto cleanup;
}
crm_debug("Writing CIB to disk");
umask(S_IWGRP | S_IWOTH | S_IROTH);
cib_file_prepare_xml(cib_root);
/* Write the CIB to a temporary file, so we can deploy (near) atomically */
fd = mkstemp(tmp_cib);
if (fd < 0) {
crm_perror(LOG_ERR, "Couldn't open temporary file %s for writing CIB",
tmp_cib);
exit_rc = pcmk_err_cib_save;
goto cleanup;
}
/* Protect the temporary file */
if (fchmod(fd, S_IRUSR | S_IWUSR) < 0) {
crm_perror(LOG_ERR, "Couldn't protect temporary file %s for writing CIB",
tmp_cib);
exit_rc = pcmk_err_cib_save;
goto cleanup;
}
if (cib_do_chown && (fchown(fd, cib_file_owner, cib_file_group) < 0)) {
crm_perror(LOG_ERR, "Couldn't protect temporary file %s for writing CIB",
tmp_cib);
exit_rc = pcmk_err_cib_save;
goto cleanup;
}
/* Write out the CIB */
if (write_xml_fd(cib_root, tmp_cib, fd, FALSE) <= 0) {
crm_err("Changes couldn't be written to %s", tmp_cib);
exit_rc = pcmk_err_cib_save;
goto cleanup;
}
/* Calculate CIB digest */
digest = calculate_on_disk_digest(cib_root);
CRM_ASSERT(digest != NULL);
crm_info("Wrote version %s.%s.0 of the CIB to disk (digest: %s)",
(admin_epoch ? admin_epoch : "0"), (epoch ? epoch : "0"), digest);
/* Write the CIB digest to a temporary file */
fd = mkstemp(tmp_digest);
if (fd < 0) {
crm_perror(LOG_ERR, "Could not create temporary file for CIB digest");
exit_rc = pcmk_err_cib_save;
goto cleanup;
}
if (cib_do_chown && (fchown(fd, cib_file_owner, cib_file_group) < 0)) {
crm_perror(LOG_ERR, "Couldn't protect temporary file %s for writing CIB",
tmp_cib);
exit_rc = pcmk_err_cib_save;
close(fd);
goto cleanup;
}
rc = pcmk__write_sync(fd, digest);
if (rc != pcmk_rc_ok) {
crm_err("Could not write digest to %s: %s",
tmp_digest, pcmk_rc_str(rc));
exit_rc = pcmk_err_cib_save;
close(fd);
goto cleanup;
}
close(fd);
crm_debug("Wrote digest %s to disk", digest);
/* Verify that what we wrote is sane */
crm_info("Reading cluster configuration file %s (digest: %s)",
tmp_cib, tmp_digest);
rc = cib_file_read_and_verify(tmp_cib, tmp_digest, NULL);
CRM_ASSERT(rc == 0);
/* Rename temporary files to live, and sync directory changes to media */
crm_debug("Activating %s", tmp_cib);
if (rename(tmp_cib, cib_path) < 0) {
crm_perror(LOG_ERR, "Couldn't rename %s as %s", tmp_cib, cib_path);
exit_rc = pcmk_err_cib_save;
}
if (rename(tmp_digest, digest_path) < 0) {
crm_perror(LOG_ERR, "Couldn't rename %s as %s", tmp_digest,
digest_path);
exit_rc = pcmk_err_cib_save;
}
pcmk__sync_directory(cib_dirname);
cleanup:
free(cib_path);
free(digest_path);
free(digest);
free(tmp_digest);
free(tmp_cib);
return exit_rc;
}
cib_t *
cib_file_new(const char *cib_location)
{
cib_file_opaque_t *private = NULL;
cib_t *cib = cib_new_variant();
if (cib == NULL) {
return NULL;
}
private = calloc(1, sizeof(cib_file_opaque_t));
if (private == NULL) {
free(cib);
return NULL;
}
cib->variant = cib_file;
cib->variant_opaque = private;
if (cib_location == NULL) {
cib_location = getenv("CIB_file");
CRM_CHECK(cib_location != NULL, return NULL); // Shouldn't be possible
}
private->flags = 0;
if (cib_file_is_live(cib_location)) {
cib_set_file_flags(private, cib_file_flag_live);
crm_trace("File %s detected as live CIB", cib_location);
}
private->filename = strdup(cib_location);
/* assign variant specific ops */
cib->delegate_fn = cib_file_perform_op_delegate;
cib->cmds->signon = cib_file_signon;
cib->cmds->signoff = cib_file_signoff;
cib->cmds->free = cib_file_free;
cib->cmds->inputfd = cib_file_inputfd;
cib->cmds->register_notification = cib_file_register_notification;
cib->cmds->set_connection_dnotify = cib_file_set_connection_dnotify;
return cib;
}
static xmlNode *in_mem_cib = NULL;
/*!
* \internal
* \brief Read CIB from disk and validate it against XML schema
*
* \param[in] filename Name of file to read CIB from
*
* \return pcmk_ok on success,
* -ENXIO if file does not exist (or stat() otherwise fails), or
* -pcmk_err_schema_validation if XML doesn't parse or validate
* \note If filename is the live CIB, this will *not* verify its digest,
* though that functionality would be trivial to add here.
* Also, this will *not* verify that the file is writable,
* because some callers might not need to write.
*/
static int
load_file_cib(const char *filename)
{
struct stat buf;
xmlNode *root = NULL;
/* Ensure file is readable */
if (strcmp(filename, "-") && (stat(filename, &buf) < 0)) {
return -ENXIO;
}
/* Parse XML from file */
root = filename2xml(filename);
if (root == NULL) {
return -pcmk_err_schema_validation;
}
/* Add a status section if not already present */
if (find_xml_node(root, XML_CIB_TAG_STATUS, FALSE) == NULL) {
create_xml_node(root, XML_CIB_TAG_STATUS);
}
/* Validate XML against its specified schema */
if (validate_xml(root, NULL, TRUE) == FALSE) {
const char *schema = crm_element_value(root, XML_ATTR_VALIDATION);
crm_err("CIB does not validate against %s", schema);
free_xml(root);
return -pcmk_err_schema_validation;
}
/* Remember the parsed XML for later use */
in_mem_cib = root;
return pcmk_ok;
}
int
cib_file_signon(cib_t * cib, const char *name, enum cib_conn_type type)
{
int rc = pcmk_ok;
cib_file_opaque_t *private = cib->variant_opaque;
if (private->filename == NULL) {
rc = -EINVAL;
} else {
rc = load_file_cib(private->filename);
}
if (rc == pcmk_ok) {
crm_debug("Opened connection to local file '%s' for %s",
private->filename, name);
cib->state = cib_connected_command;
cib->type = cib_command;
} else {
crm_info("Connection to local file '%s' for %s failed: %s\n",
private->filename, name, pcmk_strerror(rc));
}
return rc;
}
/*!
* \internal
* \brief Write out the in-memory CIB to a live CIB file
*
* param[in] path Full path to file to write
*
* \return 0 on success, -1 on failure
*/
static int
cib_file_write_live(char *path)
{
uid_t uid = geteuid();
struct passwd *daemon_pwent;
char *sep = strrchr(path, '/');
const char *cib_dirname, *cib_filename;
int rc = 0;
/* Get the desired uid/gid */
errno = 0;
daemon_pwent = getpwnam(CRM_DAEMON_USER);
if (daemon_pwent == NULL) {
crm_perror(LOG_ERR, "Could not find %s user", CRM_DAEMON_USER);
return -1;
}
/* If we're root, we can change the ownership;
* if we're daemon, anything we create will be OK;
* otherwise, block access so we don't create wrong owner
*/
if ((uid != 0) && (uid != daemon_pwent->pw_uid)) {
crm_perror(LOG_ERR, "Must be root or %s to modify live CIB",
CRM_DAEMON_USER);
return 0;
}
/* fancy footwork to separate dirname from filename
* (we know the canonical name maps to the live CIB,
* but the given name might be relative, or symlinked)
*/
if (sep == NULL) { /* no directory component specified */
cib_dirname = "./";
cib_filename = path;
} else if (sep == path) { /* given name is in / */
cib_dirname = "/";
cib_filename = path + 1;
} else { /* typical case; split given name into parts */
*sep = '\0';
cib_dirname = path;
cib_filename = sep + 1;
}
/* if we're root, we want to update the file ownership */
if (uid == 0) {
cib_file_owner = daemon_pwent->pw_uid;
cib_file_group = daemon_pwent->pw_gid;
cib_do_chown = TRUE;
}
/* write the file */
if (cib_file_write_with_digest(in_mem_cib, cib_dirname,
cib_filename) != pcmk_ok) {
rc = -1;
}
/* turn off file ownership changes, for other callers */
if (uid == 0) {
cib_do_chown = FALSE;
}
/* undo fancy stuff */
if ((sep != NULL) && (*sep == '\0')) {
*sep = '/';
}
return rc;
}
/*!
* \internal
* \brief Sign-off method for CIB file variants
*
* This will write the file to disk if needed, and free the in-memory CIB. If
* the file is the live CIB, it will compute and write a signature as well.
*
* \param[in] cib CIB object to sign off
*
* \return pcmk_ok on success, pcmk_err_generic on failure
* \todo This method should refuse to write the live CIB if the CIB manager is
* running.
*/
int
cib_file_signoff(cib_t * cib)
{
int rc = pcmk_ok;
cib_file_opaque_t *private = cib->variant_opaque;
crm_debug("Disconnecting from the CIB manager");
cib->state = cib_disconnected;
cib->type = cib_no_connection;
/* If the in-memory CIB has been changed, write it to disk */
if (pcmk_is_set(private->flags, cib_file_flag_dirty)) {
/* If this is the live CIB, write it out with a digest */
if (pcmk_is_set(private->flags, cib_file_flag_live)) {
if (cib_file_write_live(private->filename) < 0) {
rc = pcmk_err_generic;
}
/* Otherwise, it's a simple write */
} else {
gboolean do_bzip = pcmk__ends_with_ext(private->filename, ".bz2");
if (write_xml_file(in_mem_cib, private->filename, do_bzip) <= 0) {
rc = pcmk_err_generic;
}
}
if (rc == pcmk_ok) {
crm_info("Wrote CIB to %s", private->filename);
cib_clear_file_flags(private, cib_file_flag_dirty);
} else {
crm_err("Could not write CIB to %s", private->filename);
}
}
/* Free the in-memory CIB */
free_xml(in_mem_cib);
in_mem_cib = NULL;
return rc;
}
int
cib_file_free(cib_t * cib)
{
int rc = pcmk_ok;
if (cib->state != cib_disconnected) {
rc = cib_file_signoff(cib);
}
if (rc == pcmk_ok) {
cib_file_opaque_t *private = cib->variant_opaque;
free(private->filename);
free(cib->cmds);
free(private);
free(cib);
} else {
fprintf(stderr, "Couldn't sign off: %d\n", rc);
}
return rc;
}
struct cib_func_entry {
const char *op;
gboolean read_only;
cib_op_t fn;
};
/* *INDENT-OFF* */
static struct cib_func_entry cib_file_ops[] = {
{ PCMK__CIB_REQUEST_QUERY, TRUE, cib_process_query},
{CIB_OP_MODIFY, FALSE, cib_process_modify},
{CIB_OP_APPLY_DIFF, FALSE, cib_process_diff},
{ PCMK__CIB_REQUEST_BUMP, FALSE, cib_process_bump },
{CIB_OP_REPLACE, FALSE, cib_process_replace},
- {CIB_OP_CREATE, FALSE, cib_process_create},
+ { PCMK__CIB_REQUEST_CREATE, FALSE, cib_process_create },
{CIB_OP_DELETE, FALSE, cib_process_delete},
{CIB_OP_ERASE, FALSE, cib_process_erase},
{CIB_OP_UPGRADE, FALSE, cib_process_upgrade},
};
/* *INDENT-ON* */
int
cib_file_perform_op(cib_t * cib, const char *op, const char *host, const char *section,
xmlNode * data, xmlNode ** output_data, int call_options)
{
return cib_file_perform_op_delegate(cib, op, host, section, data, output_data, call_options,
NULL);
}
int
cib_file_perform_op_delegate(cib_t * cib, const char *op, const char *host, const char *section,
xmlNode * data, xmlNode ** output_data, int call_options,
const char *user_name)
{
int rc = pcmk_ok;
char *effective_user = NULL;
gboolean query = FALSE;
gboolean changed = FALSE;
xmlNode *request = NULL;
xmlNode *output = NULL;
xmlNode *cib_diff = NULL;
xmlNode *result_cib = NULL;
cib_op_t *fn = NULL;
int lpc = 0;
static int max_msg_types = PCMK__NELEM(cib_file_ops);
cib_file_opaque_t *private = cib->variant_opaque;
crm_info("Handling %s operation for %s as %s",
(op? op : "invalid"), (section? section : "entire CIB"),
(user_name? user_name : "default user"));
cib__set_call_options(call_options, "file operation",
cib_no_mtime|cib_inhibit_bcast|cib_scope_local);
if (cib->state == cib_disconnected) {
return -ENOTCONN;
}
if (output_data != NULL) {
*output_data = NULL;
}
if (op == NULL) {
return -EINVAL;
}
for (lpc = 0; lpc < max_msg_types; lpc++) {
if (pcmk__str_eq(op, cib_file_ops[lpc].op, pcmk__str_casei)) {
fn = &(cib_file_ops[lpc].fn);
query = cib_file_ops[lpc].read_only;
break;
}
}
if (fn == NULL) {
return -EPROTONOSUPPORT;
}
cib->call_id++;
request = cib_create_op(cib->call_id, "dummy-token", op, host, section, data, call_options, user_name);
if(user_name) {
crm_xml_add(request, XML_ACL_TAG_USER, user_name);
}
/* Mirror the logic in cib_prepare_common() */
if (section != NULL && data != NULL && pcmk__str_eq(crm_element_name(data), XML_TAG_CIB, pcmk__str_none)) {
data = pcmk_find_cib_element(data, section);
}
rc = cib_perform_op(op, call_options, fn, query,
section, request, data, TRUE, &changed, in_mem_cib, &result_cib, &cib_diff,
&output);
free_xml(request);
if (rc == -pcmk_err_schema_validation) {
validate_xml_verbose(result_cib);
}
if (rc != pcmk_ok) {
free_xml(result_cib);
} else if (query == FALSE) {
xml_log_patchset(LOG_DEBUG, "cib:diff", cib_diff);
free_xml(in_mem_cib);
in_mem_cib = result_cib;
cib_set_file_flags(private, cib_file_flag_dirty);
}
free_xml(cib_diff);
if (cib->op_callback != NULL) {
cib->op_callback(NULL, cib->call_id, rc, output);
}
if (output_data && output) {
if(output == in_mem_cib) {
*output_data = copy_xml(output);
} else {
*output_data = output;
}
} else if(output != in_mem_cib) {
free_xml(output);
}
free(effective_user);
return rc;
}
diff --git a/lib/cib/cib_ops.c b/lib/cib/cib_ops.c
index 16cf91b66c..4a9fdc1d5c 100644
--- a/lib/cib/cib_ops.c
+++ b/lib/cib/cib_ops.c
@@ -1,854 +1,855 @@
/*
* Copyright 2004-2022 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU Lesser General Public License
* version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
*/
#include <crm_internal.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <time.h>
#include <sys/param.h>
#include <sys/types.h>
#include <crm/crm.h>
#include <crm/cib/internal.h>
#include <crm/msg_xml.h>
#include <crm/common/xml.h>
#include <crm/common/xml_internal.h>
int
cib_process_query(const char *op, int options, const char *section, xmlNode * req, xmlNode * input,
xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer)
{
xmlNode *obj_root = NULL;
int result = pcmk_ok;
crm_trace("Processing %s for %s section",
op, pcmk__s(section, "unspecified"));
if (options & cib_xpath) {
return cib_process_xpath(op, options, section, req, input,
existing_cib, result_cib, answer);
}
CRM_CHECK(*answer == NULL, free_xml(*answer));
*answer = NULL;
if (pcmk__str_eq(XML_CIB_TAG_SECTION_ALL, section, pcmk__str_casei)) {
section = NULL;
}
obj_root = pcmk_find_cib_element(existing_cib, section);
if (obj_root == NULL) {
result = -ENXIO;
} else if (options & cib_no_children) {
const char *tag = TYPE(obj_root);
xmlNode *shallow = create_xml_node(*answer, tag);
copy_in_properties(shallow, obj_root);
*answer = shallow;
} else {
*answer = obj_root;
}
if (result == pcmk_ok && *answer == NULL) {
crm_err("Error creating query response");
result = -ENOMSG;
}
return result;
}
int
cib_process_erase(const char *op, int options, const char *section, xmlNode * req, xmlNode * input,
xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer)
{
int result = pcmk_ok;
crm_trace("Processing \"%s\" event", op);
*answer = NULL;
free_xml(*result_cib);
*result_cib = createEmptyCib(0);
copy_in_properties(*result_cib, existing_cib);
cib_update_counter(*result_cib, XML_ATTR_GENERATION_ADMIN, FALSE);
return result;
}
int
cib_process_upgrade(const char *op, int options, const char *section, xmlNode * req,
xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib,
xmlNode ** answer)
{
int rc = 0;
int new_version = 0;
int current_version = 0;
int max_version = 0;
const char *max = crm_element_value(req, F_CIB_SCHEMA_MAX);
const char *value = crm_element_value(existing_cib, XML_ATTR_VALIDATION);
*answer = NULL;
crm_trace("Processing \"%s\" event with max=%s", op, max);
if (value != NULL) {
current_version = get_schema_version(value);
}
if (max) {
max_version = get_schema_version(max);
}
rc = update_validation(result_cib, &new_version, max_version, TRUE,
!(options & cib_verbose));
if (new_version > current_version) {
cib_update_counter(*result_cib, XML_ATTR_GENERATION_ADMIN, FALSE);
cib_update_counter(*result_cib, XML_ATTR_GENERATION, TRUE);
cib_update_counter(*result_cib, XML_ATTR_NUMUPDATES, TRUE);
return pcmk_ok;
}
return rc;
}
int
cib_process_bump(const char *op, int options, const char *section, xmlNode * req, xmlNode * input,
xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer)
{
int result = pcmk_ok;
crm_trace("Processing %s for epoch='%s'", op,
pcmk__s(crm_element_value(existing_cib, XML_ATTR_GENERATION), ""));
*answer = NULL;
cib_update_counter(*result_cib, XML_ATTR_GENERATION, FALSE);
return result;
}
int
cib_update_counter(xmlNode * xml_obj, const char *field, gboolean reset)
{
char *new_value = NULL;
char *old_value = NULL;
int int_value = -1;
if (reset == FALSE && crm_element_value(xml_obj, field) != NULL) {
old_value = crm_element_value_copy(xml_obj, field);
}
if (old_value != NULL) {
int_value = atoi(old_value);
new_value = pcmk__itoa(++int_value);
} else {
new_value = strdup("1");
CRM_ASSERT(new_value != NULL);
}
crm_trace("Update %s from %s to %s",
field, pcmk__s(old_value, "unset"), new_value);
crm_xml_add(xml_obj, field, new_value);
free(new_value);
free(old_value);
return pcmk_ok;
}
int
cib_process_replace(const char *op, int options, const char *section, xmlNode * req,
xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib,
xmlNode ** answer)
{
const char *tag = NULL;
int result = pcmk_ok;
crm_trace("Processing %s for %s section",
op, pcmk__s(section, "unspecified"));
if (options & cib_xpath) {
return cib_process_xpath(op, options, section, req, input,
existing_cib, result_cib, answer);
}
*answer = NULL;
if (input == NULL) {
return -EINVAL;
}
tag = crm_element_name(input);
if (pcmk__str_eq(XML_CIB_TAG_SECTION_ALL, section, pcmk__str_casei)) {
section = NULL;
} else if (pcmk__str_eq(tag, section, pcmk__str_casei)) {
section = NULL;
}
if (pcmk__str_eq(tag, XML_TAG_CIB, pcmk__str_casei)) {
int updates = 0;
int epoch = 0;
int admin_epoch = 0;
int replace_updates = 0;
int replace_epoch = 0;
int replace_admin_epoch = 0;
const char *reason = NULL;
const char *peer = crm_element_value(req, F_ORIG);
const char *digest = crm_element_value(req, XML_ATTR_DIGEST);
if (digest) {
const char *version = crm_element_value(req, XML_ATTR_CRM_VERSION);
char *digest_verify = calculate_xml_versioned_digest(input, FALSE, TRUE,
version ? version :
CRM_FEATURE_SET);
if (!pcmk__str_eq(digest_verify, digest, pcmk__str_casei)) {
crm_err("Digest mis-match on replace from %s: %s vs. %s (expected)", peer,
digest_verify, digest);
reason = "digest mismatch";
} else {
crm_info("Digest matched on replace from %s: %s", peer, digest);
}
free(digest_verify);
} else {
crm_trace("No digest to verify");
}
cib_version_details(existing_cib, &admin_epoch, &epoch, &updates);
cib_version_details(input, &replace_admin_epoch, &replace_epoch, &replace_updates);
if (replace_admin_epoch < admin_epoch) {
reason = XML_ATTR_GENERATION_ADMIN;
} else if (replace_admin_epoch > admin_epoch) {
/* no more checks */
} else if (replace_epoch < epoch) {
reason = XML_ATTR_GENERATION;
} else if (replace_epoch > epoch) {
/* no more checks */
} else if (replace_updates < updates) {
reason = XML_ATTR_NUMUPDATES;
}
if (reason != NULL) {
crm_info("Replacement %d.%d.%d from %s not applied to %d.%d.%d:"
" current %s is greater than the replacement",
replace_admin_epoch, replace_epoch,
replace_updates, peer, admin_epoch, epoch, updates, reason);
result = -pcmk_err_old_data;
} else {
crm_info("Replaced %d.%d.%d with %d.%d.%d from %s",
admin_epoch, epoch, updates,
replace_admin_epoch, replace_epoch, replace_updates, peer);
}
free_xml(*result_cib);
*result_cib = copy_xml(input);
} else {
xmlNode *obj_root = NULL;
gboolean ok = TRUE;
obj_root = pcmk_find_cib_element(*result_cib, section);
ok = replace_xml_child(NULL, obj_root, input, FALSE);
if (ok == FALSE) {
crm_trace("No matching object to replace");
result = -ENXIO;
}
}
return result;
}
int
cib_process_delete(const char *op, int options, const char *section, xmlNode * req, xmlNode * input,
xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer)
{
xmlNode *obj_root = NULL;
crm_trace("Processing \"%s\" event", op);
if (options & cib_xpath) {
return cib_process_xpath(op, options, section, req, input,
existing_cib, result_cib, answer);
}
if (input == NULL) {
crm_err("Cannot perform modification with no data");
return -EINVAL;
}
obj_root = pcmk_find_cib_element(*result_cib, section);
if(pcmk__str_eq(crm_element_name(input), section, pcmk__str_casei)) {
xmlNode *child = NULL;
for (child = pcmk__xml_first_child(input); child;
child = pcmk__xml_next(child)) {
if (replace_xml_child(NULL, obj_root, child, TRUE) == FALSE) {
crm_trace("No matching object to delete: %s=%s", child->name, ID(child));
}
}
} else if (replace_xml_child(NULL, obj_root, input, TRUE) == FALSE) {
crm_trace("No matching object to delete: %s=%s", input->name, ID(input));
}
return pcmk_ok;
}
int
cib_process_modify(const char *op, int options, const char *section, xmlNode * req, xmlNode * input,
xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer)
{
xmlNode *obj_root = NULL;
crm_trace("Processing \"%s\" event", op);
if (options & cib_xpath) {
return cib_process_xpath(op, options, section, req, input,
existing_cib, result_cib, answer);
}
if (input == NULL) {
crm_err("Cannot perform modification with no data");
return -EINVAL;
}
obj_root = pcmk_find_cib_element(*result_cib, section);
if (obj_root == NULL) {
xmlNode *tmp_section = NULL;
const char *path = pcmk_cib_parent_name_for(section);
if (path == NULL) {
return -EINVAL;
}
tmp_section = create_xml_node(NULL, section);
- cib_process_xpath(CIB_OP_CREATE, 0, path, NULL, tmp_section, NULL, result_cib, answer);
+ cib_process_xpath(PCMK__CIB_REQUEST_CREATE, 0, path, NULL, tmp_section,
+ NULL, result_cib, answer);
free_xml(tmp_section);
obj_root = pcmk_find_cib_element(*result_cib, section);
}
CRM_CHECK(obj_root != NULL, return -EINVAL);
if (update_xml_child(obj_root, input) == FALSE) {
if (options & cib_can_create) {
add_node_copy(obj_root, input);
} else {
return -ENXIO;
}
}
if(options & cib_mixed_update) {
int max = 0, lpc;
xmlXPathObjectPtr xpathObj = xpath_search(*result_cib, "//@__delete__");
if (xpathObj) {
max = numXpathResults(xpathObj);
crm_log_xml_trace(*result_cib, "Mixed result");
}
for (lpc = 0; lpc < max; lpc++) {
xmlNode *match = getXpathResult(xpathObj, lpc);
xmlChar *match_path = xmlGetNodePath(match);
crm_debug("Destroying %s", match_path);
free(match_path);
free_xml(match);
}
freeXpathObject(xpathObj);
}
return pcmk_ok;
}
static int
update_cib_object(xmlNode * parent, xmlNode * update)
{
int result = pcmk_ok;
xmlNode *target = NULL;
xmlNode *a_child = NULL;
const char *replace = NULL;
const char *object_id = NULL;
const char *object_name = NULL;
CRM_CHECK(update != NULL, return -EINVAL);
CRM_CHECK(parent != NULL, return -EINVAL);
object_name = crm_element_name(update);
CRM_CHECK(object_name != NULL, return -EINVAL);
object_id = ID(update);
crm_trace("Processing update for <%s%s%s%s>", object_name,
((object_id == NULL)? "" : " id='"), pcmk__s(object_id, ""),
((object_id == NULL)? "" : "'"));
if (object_id == NULL) {
/* placeholder object */
target = find_xml_node(parent, object_name, FALSE);
} else {
target = pcmk__xe_match(parent, object_name, XML_ATTR_ID, object_id);
}
if (target == NULL) {
target = create_xml_node(parent, object_name);
}
crm_trace("Found node <%s%s%s%s> to update", object_name,
((object_id == NULL)? "" : " id='"), pcmk__s(object_id, ""),
((object_id == NULL)? "" : "'"));
replace = crm_element_value(update, XML_CIB_ATTR_REPLACE);
if (replace != NULL) {
xmlNode *remove = NULL;
int last = 0, lpc = 0, len = 0;
len = strlen(replace);
while (lpc <= len) {
if (replace[lpc] == ',' || replace[lpc] == 0) {
char *replace_item = NULL;
if (last == lpc) {
/* nothing to do */
last = lpc + 1;
goto incr;
}
replace_item = strndup(replace + last, lpc - last);
remove = find_xml_node(target, replace_item, FALSE);
if (remove != NULL) {
crm_trace("Replacing node <%s> in <%s>",
replace_item, crm_element_name(target));
free_xml(remove);
remove = NULL;
}
free(replace_item);
last = lpc + 1;
}
incr:
lpc++;
}
xml_remove_prop(update, XML_CIB_ATTR_REPLACE);
xml_remove_prop(target, XML_CIB_ATTR_REPLACE);
}
copy_in_properties(target, update);
if (xml_acl_denied(target)) {
crm_notice("Cannot update <%s id=%s>", pcmk__s(object_name, "<null>"), pcmk__s(object_id, "<null>"));
return -EACCES;
}
crm_trace("Processing children of <%s%s%s%s>", object_name,
((object_id == NULL)? "" : " id='"), pcmk__s(object_id, ""),
((object_id == NULL)? "" : "'"));
for (a_child = pcmk__xml_first_child(update); a_child != NULL;
a_child = pcmk__xml_next(a_child)) {
int tmp_result = 0;
crm_trace("Updating child <%s%s%s%s>", crm_element_name(a_child),
((ID(a_child) == NULL)? "" : " id='"),
pcmk__s(ID(a_child), ""), ((ID(a_child) == NULL)? "" : "'"));
tmp_result = update_cib_object(target, a_child);
/* only the first error is likely to be interesting */
if (tmp_result != pcmk_ok) {
crm_err("Error updating child <%s%s%s%s>",
crm_element_name(a_child),
((ID(a_child) == NULL)? "" : " id='"),
pcmk__s(ID(a_child), ""),
((ID(a_child) == NULL)? "" : "'"));
if (result == pcmk_ok) {
result = tmp_result;
}
}
}
crm_trace("Finished handling update for <%s%s%s%s>", object_name,
((object_id == NULL)? "" : " id='"), pcmk__s(object_id, ""),
((object_id == NULL)? "" : "'"));
return result;
}
static int
add_cib_object(xmlNode * parent, xmlNode * new_obj)
{
const char *object_name = NULL;
const char *object_id = NULL;
xmlNode *equiv_node = NULL;
if ((parent == NULL) || (new_obj == NULL)) {
return -EINVAL;
}
object_name = crm_element_name(new_obj);
if (object_name == NULL) {
return -EINVAL;
}
object_id = ID(new_obj);
crm_trace("Processing creation of <%s%s%s%s>", object_name,
((object_id == NULL)? "" : " id='"), pcmk__s(object_id, ""),
((object_id == NULL)? "" : "'"));
if (object_id == NULL) {
equiv_node = find_xml_node(parent, object_name, FALSE);
} else {
equiv_node = pcmk__xe_match(parent, object_name, XML_ATTR_ID,
object_id);
}
if (equiv_node != NULL) {
return -EEXIST;
}
return update_cib_object(parent, new_obj);
}
int
cib_process_create(const char *op, int options, const char *section, xmlNode * req, xmlNode * input,
xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer)
{
xmlNode *failed = NULL;
int result = pcmk_ok;
xmlNode *update_section = NULL;
crm_trace("Processing %s for %s section",
op, pcmk__s(section, "unspecified"));
if (pcmk__str_eq(XML_CIB_TAG_SECTION_ALL, section, pcmk__str_casei)) {
section = NULL;
} else if (pcmk__str_eq(XML_TAG_CIB, section, pcmk__str_casei)) {
section = NULL;
} else if (pcmk__str_eq(crm_element_name(input), XML_TAG_CIB, pcmk__str_casei)) {
section = NULL;
}
- CRM_CHECK(strcasecmp(CIB_OP_CREATE, op) == 0, return -EINVAL);
+ CRM_CHECK(strcmp(op, PCMK__CIB_REQUEST_CREATE) == 0, return -EINVAL);
if (input == NULL) {
crm_err("Cannot perform modification with no data");
return -EINVAL;
}
if (section == NULL) {
return cib_process_modify(op, options, section, req, input, existing_cib, result_cib,
answer);
}
failed = create_xml_node(NULL, XML_TAG_FAILED);
update_section = pcmk_find_cib_element(*result_cib, section);
if (pcmk__str_eq(crm_element_name(input), section, pcmk__str_casei)) {
xmlNode *a_child = NULL;
for (a_child = pcmk__xml_first_child(input); a_child != NULL;
a_child = pcmk__xml_next(a_child)) {
result = add_cib_object(update_section, a_child);
if (update_results(failed, a_child, op, result)) {
break;
}
}
} else {
result = add_cib_object(update_section, input);
update_results(failed, input, op, result);
}
if ((result == pcmk_ok) && xml_has_children(failed)) {
result = -EINVAL;
}
if (result != pcmk_ok) {
crm_log_xml_err(failed, "CIB Update failures");
*answer = failed;
} else {
free_xml(failed);
}
return result;
}
int
cib_process_diff(const char *op, int options, const char *section, xmlNode * req, xmlNode * input,
xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer)
{
const char *originator = NULL;
if (req != NULL) {
originator = crm_element_value(req, F_ORIG);
}
crm_trace("Processing \"%s\" event from %s%s",
op, originator,
(pcmk_is_set(options, cib_force_diff)? " (global update)" : ""));
free_xml(*result_cib);
*result_cib = copy_xml(existing_cib);
return xml_apply_patchset(*result_cib, input, TRUE);
}
gboolean
cib_config_changed(xmlNode * last, xmlNode * next, xmlNode ** diff)
{
int lpc = 0, max = 0;
gboolean config_changes = FALSE;
xmlXPathObject *xpathObj = NULL;
int format = 1;
CRM_ASSERT(diff != NULL);
if (*diff == NULL && last != NULL && next != NULL) {
*diff = diff_xml_object(last, next, FALSE);
}
if (*diff == NULL) {
goto done;
}
crm_element_value_int(*diff, "format", &format);
/* This function only applies to v1 diffs. */
CRM_LOG_ASSERT(format == 1);
xpathObj = xpath_search(*diff, "//" XML_CIB_TAG_CONFIGURATION);
if (numXpathResults(xpathObj) > 0) {
config_changes = TRUE;
goto done;
}
freeXpathObject(xpathObj);
/*
* Do not check XML_TAG_DIFF_ADDED "//" XML_TAG_CIB
* This always contains every field and would produce a false positive
* every time if the checked value existed
*/
xpathObj = xpath_search(*diff, "//" XML_TAG_DIFF_REMOVED "//" XML_TAG_CIB);
max = numXpathResults(xpathObj);
for (lpc = 0; lpc < max; lpc++) {
xmlNode *top = getXpathResult(xpathObj, lpc);
if (crm_element_value(top, XML_ATTR_GENERATION) != NULL) {
config_changes = TRUE;
goto done;
}
if (crm_element_value(top, XML_ATTR_GENERATION_ADMIN) != NULL) {
config_changes = TRUE;
goto done;
}
if (crm_element_value(top, XML_ATTR_VALIDATION) != NULL) {
config_changes = TRUE;
goto done;
}
if (crm_element_value(top, XML_ATTR_CRM_VERSION) != NULL) {
config_changes = TRUE;
goto done;
}
if (crm_element_value(top, "remote-clear-port") != NULL) {
config_changes = TRUE;
goto done;
}
if (crm_element_value(top, "remote-tls-port") != NULL) {
config_changes = TRUE;
goto done;
}
}
done:
freeXpathObject(xpathObj);
return config_changes;
}
int
cib_process_xpath(const char *op, int options, const char *section, xmlNode * req, xmlNode * input,
xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer)
{
int lpc = 0;
int max = 0;
int rc = pcmk_ok;
bool is_query = pcmk__str_eq(op, PCMK__CIB_REQUEST_QUERY, pcmk__str_none);
xmlXPathObjectPtr xpathObj = NULL;
crm_trace("Processing \"%s\" event", op);
if (is_query) {
xpathObj = xpath_search(existing_cib, section);
} else {
xpathObj = xpath_search(*result_cib, section);
}
max = numXpathResults(xpathObj);
if (max < 1 && pcmk__str_eq(op, CIB_OP_DELETE, pcmk__str_casei)) {
crm_debug("%s was already removed", section);
} else if (max < 1) {
crm_debug("%s: %s does not exist", op, section);
rc = -ENXIO;
} else if (is_query) {
if (max > 1) {
*answer = create_xml_node(NULL, "xpath-query");
}
}
if (pcmk__str_eq(op, CIB_OP_DELETE, pcmk__str_casei) && (options & cib_multiple)) {
dedupXpathResults(xpathObj);
}
for (lpc = 0; lpc < max; lpc++) {
xmlChar *path = NULL;
xmlNode *match = getXpathResult(xpathObj, lpc);
if (match == NULL) {
continue;
}
path = xmlGetNodePath(match);
crm_debug("Processing %s op for %s with %s", op, section, path);
free(path);
if (pcmk__str_eq(op, CIB_OP_DELETE, pcmk__str_casei)) {
if (match == *result_cib) {
/* Attempting to delete the whole "/cib" */
crm_warn("Cannot perform %s for %s: The xpath is addressing the whole /cib", op, section);
rc = -EINVAL;
break;
}
free_xml(match);
if ((options & cib_multiple) == 0) {
break;
}
} else if (pcmk__str_eq(op, CIB_OP_MODIFY, pcmk__str_casei)) {
if (update_xml_child(match, input) == FALSE) {
rc = -ENXIO;
} else if ((options & cib_multiple) == 0) {
break;
}
- } else if (pcmk__str_eq(op, CIB_OP_CREATE, pcmk__str_casei)) {
+ } else if (pcmk__str_eq(op, PCMK__CIB_REQUEST_CREATE, pcmk__str_none)) {
add_node_copy(match, input);
break;
} else if (pcmk__str_eq(op, PCMK__CIB_REQUEST_QUERY, pcmk__str_none)) {
if (options & cib_no_children) {
const char *tag = TYPE(match);
xmlNode *shallow = create_xml_node(*answer, tag);
copy_in_properties(shallow, match);
if (*answer == NULL) {
*answer = shallow;
}
} else if (options & cib_xpath_address) {
char *path = NULL;
xmlNode *parent = match;
while (parent && parent->type == XML_ELEMENT_NODE) {
const char *id = crm_element_value(parent, XML_ATTR_ID);
char *new_path = NULL;
if (id) {
new_path = crm_strdup_printf("/%s[@id='%s']%s",
parent->name, id,
(path? path : ""));
} else {
new_path = crm_strdup_printf("/%s%s", parent->name,
(path? path : ""));
}
free(path);
path = new_path;
parent = parent->parent;
}
crm_trace("Got: %s", path);
if (*answer == NULL) {
*answer = create_xml_node(NULL, "xpath-query");
}
parent = create_xml_node(*answer, "xpath-query-path");
crm_xml_add(parent, XML_ATTR_ID, path);
free(path);
} else if (*answer) {
add_node_copy(*answer, match);
} else {
*answer = match;
}
} else if (pcmk__str_eq(op, CIB_OP_REPLACE, pcmk__str_casei)) {
xmlNode *parent = match->parent;
free_xml(match);
if (input != NULL) {
add_node_copy(parent, input);
}
if ((options & cib_multiple) == 0) {
break;
}
}
}
freeXpathObject(xpathObj);
return rc;
}
/* remove this function */
gboolean
update_results(xmlNode * failed, xmlNode * target, const char *operation, int return_code)
{
xmlNode *xml_node = NULL;
gboolean was_error = FALSE;
const char *error_msg = NULL;
if (return_code != pcmk_ok) {
error_msg = pcmk_strerror(return_code);
was_error = TRUE;
xml_node = create_xml_node(failed, XML_FAIL_TAG_CIB);
add_node_copy(xml_node, target);
crm_xml_add(xml_node, XML_FAILCIB_ATTR_ID, ID(target));
crm_xml_add(xml_node, XML_FAILCIB_ATTR_OBJTYPE, TYPE(target));
crm_xml_add(xml_node, XML_FAILCIB_ATTR_OP, operation);
crm_xml_add(xml_node, XML_FAILCIB_ATTR_REASON, error_msg);
crm_warn("Action %s failed: %s (cde=%d)", operation, error_msg, return_code);
}
return was_error;
}
diff --git a/tools/cibadmin.c b/tools/cibadmin.c
index 7b966bb84d..ab21a0b254 100644
--- a/tools/cibadmin.c
+++ b/tools/cibadmin.c
@@ -1,918 +1,918 @@
/*
* Copyright 2004-2022 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU General Public License version 2
* or later (GPLv2+) WITHOUT ANY WARRANTY.
*/
#include <crm_internal.h>
#include <stdio.h>
#include <crm/crm.h>
#include <crm/msg_xml.h>
#include <crm/common/xml.h>
#include <crm/common/ipc.h>
#include <crm/cib/internal.h>
#include <pacemaker-internal.h>
static int message_timeout_ms = 30;
static int command_options = 0;
static int request_id = 0;
static int bump_log_num = 0;
static char *host = NULL;
static const char *cib_user = NULL;
static const char *cib_action = NULL;
static const char *obj_type = NULL;
static cib_t *the_cib = NULL;
static GMainLoop *mainloop = NULL;
static gboolean force_flag = FALSE;
static crm_exit_t exit_code = CRM_EX_OK;
int do_init(void);
int do_work(xmlNode *input, int command_options, xmlNode **output);
void cibadmin_op_callback(xmlNode *msg, int call_id, int rc, xmlNode *output,
void *user_data);
static pcmk__cli_option_t long_options[] = {
// long option, argument type, storage, short option, description, flags
{
"help", no_argument, NULL, '?',
"\tThis text", pcmk__option_default
},
{
"version", no_argument, NULL, '$',
"\tVersion information", pcmk__option_default
},
{
"verbose", no_argument, NULL, 'V',
"\tIncrease debug output\n", pcmk__option_default
},
{
"-spacer-", no_argument, NULL, '-',
"Commands:", pcmk__option_default
},
{
"upgrade", no_argument, NULL, 'u',
"\tUpgrade the configuration to the latest syntax", pcmk__option_default
},
{
"query", no_argument, NULL, 'Q',
"\tQuery the contents of the CIB", pcmk__option_default
},
{
"erase", no_argument, NULL, 'E',
"\tErase the contents of the whole CIB", pcmk__option_default
},
{
"bump", no_argument, NULL, 'B',
"\tIncrease the CIB's epoch value by 1", pcmk__option_default
},
{
"create", no_argument, NULL, 'C',
"\tCreate an object in the CIB (will fail if object already exists)",
pcmk__option_default
},
{
"modify", no_argument, NULL, 'M',
"\tFind object somewhere in CIB's XML tree and update it "
"(fails if object does not exist unless -c is also specified)",
pcmk__option_default
},
{
"patch", no_argument, NULL, 'P',
"\tSupply an update in the form of an XML diff (see crm_diff(8))",
pcmk__option_default
},
{
"replace", no_argument, NULL, 'R',
"\tRecursively replace an object in the CIB", pcmk__option_default
},
{
"delete", no_argument, NULL, 'D',
"\tDelete first object matching supplied criteria "
"(for example, <op id=\"rsc1_op1\" name=\"monitor\"/>)",
pcmk__option_default
},
{
"-spacer-", no_argument, NULL, '-',
"\n\tThe XML element name and all attributes must match "
"in order for the element to be deleted.\n",
pcmk__option_default
},
{
"delete-all", no_argument, NULL, 'd',
"When used with --xpath, remove all matching objects in the "
"configuration instead of just the first one",
pcmk__option_default
},
{
"empty", no_argument, NULL, 'a',
"\tOutput an empty CIB", pcmk__option_default
},
{
"md5-sum", no_argument, NULL, '5',
"\tCalculate the on-disk CIB digest", pcmk__option_default
},
{
"md5-sum-versioned", no_argument, NULL, '6',
"Calculate an on-the-wire versioned CIB digest", pcmk__option_default
},
{
"show-access", optional_argument, NULL, 'S',
"Whether to use syntax highlighting for ACLs "
"(with -Q/--query and -U/--user)",
pcmk__option_default
},
{
"-spacer-", no_argument, NULL, '-',
"\n\tThat amounts to one of \"color\" (default for terminal),"
" \"text\" (otherwise), \"namespace\", or \"auto\""
" (per former defaults).",
pcmk__option_default
},
{
"blank", no_argument, NULL, '-',
NULL, pcmk__option_hidden
},
{
"-spacer-", required_argument, NULL, '-',
"\nAdditional options:", pcmk__option_default
},
{
"force", no_argument, NULL, 'f',
NULL, pcmk__option_default
},
{
"timeout", required_argument, NULL, 't',
"Time (in seconds) to wait before declaring the operation failed",
pcmk__option_default
},
{
"user", required_argument, NULL, 'U',
"Run the command with permissions of the named user (valid only for "
"the root and " CRM_DAEMON_USER " accounts)",
pcmk__option_default
},
{
"sync-call", no_argument, NULL, 's',
"Wait for call to complete before returning", pcmk__option_default
},
{
"local", no_argument, NULL, 'l',
"\tCommand takes effect locally (should be used only for queries)",
pcmk__option_default
},
{
"allow-create", no_argument, NULL, 'c',
"(Advanced) Allow target of --modify/-M to be created "
"if it does not exist",
pcmk__option_default
},
{
"no-children", no_argument, NULL, 'n',
"(Advanced) When querying an object, do not include its children "
"in the result",
pcmk__option_default
},
{
"no-bcast", no_argument, NULL, 'b',
NULL, pcmk__option_hidden
},
{
"-spacer-", no_argument, NULL, '-',
"\nData:", pcmk__option_default
},
{
"xml-text", required_argument, NULL, 'X',
"Retrieve XML from the supplied string", pcmk__option_default
},
{
"xml-file", required_argument, NULL, 'x',
"Retrieve XML from the named file", pcmk__option_default
},
{
"xml-pipe", no_argument, NULL, 'p',
"Retrieve XML from stdin\n", pcmk__option_default
},
{
"scope", required_argument, NULL, 'o',
"Limit scope of operation to specific section of CIB",
pcmk__option_default
},
{
"-spacer-", no_argument, NULL, '-',
"\tValid values: configuration, nodes, resources, constraints, "
"crm_config, rsc_defaults, op_defaults, acls, fencing-topology, "
"tags, alerts",
pcmk__option_default
},
{
"xpath", required_argument, NULL, 'A',
"A valid XPath to use instead of --scope/-o", pcmk__option_default
},
{
"node-path", no_argument, NULL, 'e',
"When performing XPath queries, return path of any matches found",
pcmk__option_default
},
{
"-spacer-", no_argument, NULL, '-',
"\t(for example, \"/cib/configuration/resources/clone[@id='ms_RH1_SCS']"
"/primitive[@id='prm_RH1_SCS']\")",
pcmk__option_paragraph
},
{
"node", required_argument, NULL, 'N',
"(Advanced) Send command to the specified host", pcmk__option_default
},
{
"-spacer-", no_argument, NULL, '!',
NULL, pcmk__option_hidden
},
{
"-spacer-", no_argument, NULL, '-',
"\n\nExamples:\n", pcmk__option_default
},
{
"-spacer-", no_argument, NULL, '-',
"Query the configuration from the local node:", pcmk__option_paragraph
},
{
"-spacer-", no_argument, NULL, '-',
" cibadmin --query --local", pcmk__option_example
},
{
"-spacer-", no_argument, NULL, '-',
"Query just the cluster options configuration:", pcmk__option_paragraph
},
{
"-spacer-", no_argument, NULL, '-',
" cibadmin --query --scope crm_config", pcmk__option_example
},
{
"-spacer-", no_argument, NULL, '-',
"Query all 'target-role' settings:", pcmk__option_paragraph
},
{
"-spacer-", no_argument, NULL, '-',
" cibadmin --query --xpath \"//nvpair[@name='target-role']\"",
pcmk__option_example
},
{
"-spacer-", no_argument, NULL, '-',
"Remove all 'is-managed' settings:", pcmk__option_paragraph
},
{
"-spacer-", no_argument, NULL, '-',
" cibadmin --delete-all --xpath \"//nvpair[@name='is-managed']\"",
pcmk__option_example
},
{
"-spacer-", no_argument, NULL, '-',
"Remove the resource named 'old':", pcmk__option_paragraph
},
{
"-spacer-", no_argument, NULL, '-',
" cibadmin --delete --xml-text '<primitive id=\"old\"/>'",
pcmk__option_example
},
{
"-spacer-", no_argument, NULL, '-',
"Remove all resources from the configuration:", pcmk__option_paragraph
},
{
"-spacer-", no_argument, NULL, '-',
" cibadmin --replace --scope resources --xml-text '<resources/>'",
pcmk__option_example
},
{
"-spacer-", no_argument, NULL, '-',
"Replace complete configuration with contents of $HOME/pacemaker.xml:",
pcmk__option_paragraph
},
{
"-spacer-", no_argument, NULL, '-',
" cibadmin --replace --xml-file $HOME/pacemaker.xml",
pcmk__option_example
},
{
"-spacer-", no_argument, NULL, '-',
"Replace constraints section of configuration with contents of "
"$HOME/constraints.xml:",
pcmk__option_paragraph
},
{
"-spacer-", no_argument, NULL, '-',
" cibadmin --replace --scope constraints --xml-file "
"$HOME/constraints.xml",
pcmk__option_example
},
{
"-spacer-", no_argument, NULL, '-',
"Increase configuration version to prevent old configurations from "
"being loaded accidentally:",
pcmk__option_paragraph
},
{
"-spacer-", no_argument, NULL, '-',
" cibadmin --modify --xml-text '<cib admin_epoch=\"admin_epoch++\"/>'",
pcmk__option_example
},
{
"-spacer-", no_argument, NULL, '-',
"Edit the configuration with your favorite $EDITOR:",
pcmk__option_paragraph
},
{
"-spacer-", no_argument, NULL, '-',
" cibadmin --query > $HOME/local.xml", pcmk__option_example
},
{
"-spacer-", no_argument, NULL, '-',
" $EDITOR $HOME/local.xml", pcmk__option_example
},
{
"-spacer-", no_argument, NULL, '-',
" cibadmin --replace --xml-file $HOME/local.xml", pcmk__option_example
},
{
"-spacer-", no_argument, NULL, '-',
"Assuming terminal, render configuration in color (green for writable, blue for readable, red for denied) to visualize permissions for user tony:",
pcmk__option_paragraph
},
{
"-spacer-", no_argument, NULL, '-',
" cibadmin --show-access=color --query --user tony | less -r",
pcmk__option_example
},
{
"-spacer-", no_argument, NULL, '-',
"SEE ALSO:", pcmk__option_default
},
{
"-spacer-", no_argument, NULL, '-',
" crm(8), pcs(8), crm_shadow(8), crm_diff(8)", pcmk__option_default
},
{
"host", required_argument, NULL, 'h',
"deprecated", pcmk__option_hidden
},
{ 0, 0, 0, 0 }
};
static void
print_xml_output(xmlNode * xml)
{
char *buffer;
if (!xml) {
return;
} else if (xml->type != XML_ELEMENT_NODE) {
return;
}
if (command_options & cib_xpath_address) {
const char *id = crm_element_value(xml, XML_ATTR_ID);
if (pcmk__str_eq((const char *)xml->name, "xpath-query", pcmk__str_casei)) {
xmlNode *child = NULL;
for (child = xml->children; child; child = child->next) {
print_xml_output(child);
}
} else if (id) {
printf("%s\n", id);
}
} else {
buffer = dump_xml_formatted(xml);
fprintf(stdout, "%s", pcmk__s(buffer, "<null>\n"));
free(buffer);
}
}
// Upgrade requested but already at latest schema
static void
report_schema_unchanged(void)
{
const char *err = pcmk_rc_str(pcmk_rc_schema_unchanged);
crm_info("Upgrade unnecessary: %s\n", err);
printf("Upgrade unnecessary: %s\n", err);
exit_code = CRM_EX_OK;
}
int
main(int argc, char **argv)
{
int argerr = 0;
int rc = pcmk_ok;
int flag;
const char *source = NULL;
const char *admin_input_xml = NULL;
const char *admin_input_file = NULL;
gboolean dangerous_cmd = FALSE;
gboolean admin_input_stdin = FALSE;
xmlNode *output = NULL;
xmlNode *input = NULL;
char *username = NULL;
const char *acl_cred = NULL;
enum acl_eval_how {
acl_eval_unused,
acl_eval_auto,
acl_eval_namespace,
acl_eval_text,
acl_eval_color,
} acl_eval_how = acl_eval_unused;
int option_index = 0;
pcmk__cli_init_logging("cibadmin", 0);
set_crm_log_level(LOG_CRIT);
pcmk__set_cli_options(NULL, "<command> [options]", long_options,
"query and edit the Pacemaker configuration");
if (argc < 2) {
pcmk__cli_help('?', CRM_EX_USAGE);
}
while (1) {
flag = pcmk__next_cli_option(argc, argv, &option_index, NULL);
if (flag == -1)
break;
switch (flag) {
case 't':
message_timeout_ms = atoi(optarg);
if (message_timeout_ms < 1) {
message_timeout_ms = 30;
}
break;
case 'A':
obj_type = optarg;
cib__set_call_options(command_options, crm_system_name,
cib_xpath);
break;
case 'e':
cib__set_call_options(command_options, crm_system_name,
cib_xpath_address);
break;
case 'u':
cib_action = CIB_OP_UPGRADE;
dangerous_cmd = TRUE;
break;
case 'E':
cib_action = CIB_OP_ERASE;
dangerous_cmd = TRUE;
break;
case 'S':
if (optarg != NULL) {
if (!strcmp(optarg, "auto")) {
acl_eval_how = acl_eval_auto;
} else if (!strcmp(optarg, "namespace")) {
acl_eval_how = acl_eval_namespace;
} else if (!strcmp(optarg, "text")) {
acl_eval_how = acl_eval_text;
} else if (!strcmp(optarg, "color")) {
acl_eval_how = acl_eval_color;
} else {
fprintf(stderr, "Unrecognized value for --show-access: \"%s\"\n",
optarg);
++argerr;
}
} else {
acl_eval_how = acl_eval_auto;
}
/* XXX this is a workaround until we unify happy paths for
both a/sync handling; the respective extra code is
only in sync path now, but does it matter at all for
query-like request wrt. what blackbox users observe? */
command_options |= cib_sync_call;
break;
case 'Q':
cib_action = PCMK__CIB_REQUEST_QUERY;
break;
case 'P':
cib_action = CIB_OP_APPLY_DIFF;
break;
case 'U':
cib_user = optarg;
break;
case 'M':
cib_action = CIB_OP_MODIFY;
break;
case 'R':
cib_action = CIB_OP_REPLACE;
break;
case 'C':
- cib_action = CIB_OP_CREATE;
+ cib_action = PCMK__CIB_REQUEST_CREATE;
break;
case 'D':
cib_action = CIB_OP_DELETE;
break;
case '5':
cib_action = "md5-sum";
break;
case '6':
cib_action = "md5-sum-versioned";
break;
case 'c':
cib__set_call_options(command_options, crm_system_name,
cib_can_create);
break;
case 'n':
cib__set_call_options(command_options, crm_system_name,
cib_no_children);
break;
case 'B':
cib_action = PCMK__CIB_REQUEST_BUMP;
crm_log_args(argc, argv);
break;
case 'V':
cib__set_call_options(command_options, crm_system_name,
cib_verbose);
bump_log_num++;
break;
case '?':
case '$':
case '!':
pcmk__cli_help(flag, CRM_EX_OK);
break;
case 'o':
crm_trace("Option %c => %s", flag, optarg);
obj_type = optarg;
break;
case 'X':
crm_trace("Option %c => %s", flag, optarg);
admin_input_xml = optarg;
crm_log_args(argc, argv);
break;
case 'x':
crm_trace("Option %c => %s", flag, optarg);
admin_input_file = optarg;
crm_log_args(argc, argv);
break;
case 'p':
admin_input_stdin = TRUE;
crm_log_args(argc, argv);
break;
case 'N':
case 'h':
pcmk__str_update(&host, optarg);
break;
case 'l':
cib__set_call_options(command_options, crm_system_name,
cib_scope_local);
break;
case 'd':
cib_action = CIB_OP_DELETE;
cib__set_call_options(command_options, crm_system_name,
cib_multiple);
dangerous_cmd = TRUE;
break;
case 'b':
dangerous_cmd = TRUE;
cib__set_call_options(command_options, crm_system_name,
cib_inhibit_bcast|cib_scope_local);
break;
case 's':
cib__set_call_options(command_options, crm_system_name,
cib_sync_call);
break;
case 'f':
force_flag = TRUE;
cib__set_call_options(command_options, crm_system_name,
cib_quorum_override);
crm_log_args(argc, argv);
break;
case 'a':
output = createEmptyCib(1);
if (optind < argc) {
crm_xml_add(output, XML_ATTR_VALIDATION, argv[optind]);
}
admin_input_xml = dump_xml_formatted(output);
fprintf(stdout, "%s", pcmk__s(admin_input_xml, "<null>\n"));
crm_exit(CRM_EX_OK);
break;
default:
printf("Argument code 0%o (%c)" " is not (?yet?) supported\n", flag, flag);
++argerr;
break;
}
}
while (bump_log_num > 0) {
crm_bump_log_level(argc, argv);
bump_log_num--;
}
if (optind < argc) {
printf("non-option ARGV-elements: ");
while (optind < argc)
printf("%s ", argv[optind++]);
printf("\n");
pcmk__cli_help('?', CRM_EX_USAGE);
}
if (optind > argc || cib_action == NULL) {
++argerr;
}
if (argerr) {
pcmk__cli_help('?', CRM_EX_USAGE);
}
if (dangerous_cmd && force_flag == FALSE) {
fprintf(stderr, "The supplied command is considered dangerous."
" To prevent accidental destruction of the cluster,"
" the --force flag is required in order to proceed.\n");
fflush(stderr);
crm_exit(CRM_EX_UNSAFE);
}
if (admin_input_file != NULL) {
input = filename2xml(admin_input_file);
source = admin_input_file;
} else if (admin_input_xml != NULL) {
source = "input string";
input = string2xml(admin_input_xml);
} else if (admin_input_stdin) {
source = "STDIN";
input = stdin2xml();
} else if (acl_eval_how != acl_eval_unused) {
username = pcmk__uid2username(geteuid());
if (pcmk_acl_required(username)) {
if (force_flag) {
fprintf(stderr, "The supplied command can provide skewed"
" result since it is run under user that also"
" gets guarded per ACLs on their own right."
" Continuing since --force flag was"
" provided.\n");
} else {
fprintf(stderr, "The supplied command can provide skewed"
" result since it is run under user that also"
" gets guarded per ACLs in their own right."
" To accept the risk of such a possible"
" distortion (without even knowing it at this"
" time), use the --force flag.\n");
crm_exit(CRM_EX_UNSAFE);
}
}
free(username);
username = NULL;
if (cib_user == NULL) {
fprintf(stderr, "The supplied command requires -U user specified.\n");
crm_exit(CRM_EX_USAGE);
}
/* we already stopped/warned ACL-controlled users about consequences */
acl_cred = cib_user;
cib_user = NULL;
}
if (input != NULL) {
crm_log_xml_debug(input, "[admin input]");
} else if (source) {
fprintf(stderr, "Couldn't parse input from %s.\n", source);
crm_exit(CRM_EX_CONFIG);
}
if (pcmk__str_eq(cib_action, "md5-sum", pcmk__str_casei)) {
char *digest = NULL;
if (input == NULL) {
fprintf(stderr, "Please supply XML to process with -X, -x or -p\n");
crm_exit(CRM_EX_USAGE);
}
digest = calculate_on_disk_digest(input);
fprintf(stderr, "Digest: ");
fprintf(stdout, "%s\n", pcmk__s(digest, "<null>"));
free(digest);
free_xml(input);
crm_exit(CRM_EX_OK);
} else if (pcmk__str_eq(cib_action, "md5-sum-versioned", pcmk__str_casei)) {
char *digest = NULL;
const char *version = NULL;
if (input == NULL) {
fprintf(stderr, "Please supply XML to process with -X, -x or -p\n");
crm_exit(CRM_EX_USAGE);
}
version = crm_element_value(input, XML_ATTR_CRM_VERSION);
digest = calculate_xml_versioned_digest(input, FALSE, TRUE, version);
fprintf(stderr, "Versioned (%s) digest: ", version);
fprintf(stdout, "%s\n", pcmk__s(digest, "<null>"));
free(digest);
free_xml(input);
crm_exit(CRM_EX_OK);
}
rc = do_init();
if (rc != pcmk_ok) {
crm_err("Init failed, could not perform requested operations");
fprintf(stderr, "Init failed, could not perform requested operations\n");
free_xml(input);
crm_exit(pcmk_rc2exitc(pcmk_legacy2rc(rc)));
}
rc = do_work(input, command_options, &output);
if (rc > 0) {
/* wait for the reply by creating a mainloop and running it until
* the callbacks are invoked...
*/
request_id = rc;
the_cib->cmds->register_callback(the_cib, request_id, message_timeout_ms, FALSE, NULL,
"cibadmin_op_callback", cibadmin_op_callback);
mainloop = g_main_loop_new(NULL, FALSE);
crm_trace("%s waiting for reply from the local CIB", crm_system_name);
crm_info("Starting mainloop");
g_main_loop_run(mainloop);
} else if ((rc == -pcmk_err_schema_unchanged)
&& pcmk__str_eq(cib_action, CIB_OP_UPGRADE, pcmk__str_none)) {
report_schema_unchanged();
} else if (rc < 0) {
rc = pcmk_legacy2rc(rc);
crm_err("Call failed: %s", pcmk_rc_str(rc));
fprintf(stderr, "Call failed: %s\n", pcmk_rc_str(rc));
if (rc == pcmk_rc_schema_validation) {
if (pcmk__str_eq(cib_action, CIB_OP_UPGRADE, pcmk__str_none)) {
xmlNode *obj = NULL;
int version = 0;
if (the_cib->cmds->query(the_cib, NULL, &obj,
command_options) == pcmk_ok) {
update_validation(&obj, &version, 0, TRUE, FALSE);
}
} else if (output) {
validate_xml_verbose(output);
}
}
exit_code = pcmk_rc2exitc(rc);
}
if (output != NULL && acl_eval_how != acl_eval_unused) {
xmlDoc *acl_evaled_doc;
rc = pcmk__acl_annotate_permissions(acl_cred, output->doc, &acl_evaled_doc);
if (rc == pcmk_rc_ok) {
enum pcmk__acl_render_how how;
xmlChar *rendered = NULL;
free_xml(output);
switch(acl_eval_how) {
case acl_eval_text:
how = pcmk__acl_render_text;
break;
case acl_eval_color:
how = pcmk__acl_render_color;
break;
case acl_eval_namespace:
how = pcmk__acl_render_namespace;
break;
default:
if (/*acl_eval_auto*/ isatty(STDOUT_FILENO)) {
how = pcmk__acl_render_color;
} else {
how = pcmk__acl_render_text;
}
break;
}
if (!pcmk__acl_evaled_render(acl_evaled_doc, how,
&rendered)) {
printf("%s\n", (char *) rendered);
free(rendered);
} else {
fprintf(stderr, "Could not render evaluated access\n");
crm_exit(CRM_EX_CONFIG);
}
output = NULL;
} else {
fprintf(stderr, "Could not evaluate access per request (%s, error: %s)\n", acl_cred, pcmk_rc_str(rc));
crm_exit(CRM_EX_CONFIG);
}
}
if (output != NULL) {
print_xml_output(output);
free_xml(output);
}
crm_trace("%s exiting normally", crm_system_name);
free_xml(input);
rc = cib__clean_up_connection(&the_cib);
if (exit_code == CRM_EX_OK) {
exit_code = pcmk_rc2exitc(rc);
}
free(host);
crm_exit(exit_code);
}
int
do_work(xmlNode * input, int call_options, xmlNode ** output)
{
/* construct the request */
the_cib->call_timeout = message_timeout_ms;
if (strcasecmp(CIB_OP_REPLACE, cib_action) == 0
&& pcmk__str_eq(crm_element_name(input), XML_TAG_CIB, pcmk__str_casei)) {
xmlNode *status = pcmk_find_cib_element(input, XML_CIB_TAG_STATUS);
if (status == NULL) {
create_xml_node(input, XML_CIB_TAG_STATUS);
}
}
if (cib_action != NULL) {
crm_trace("Passing \"%s\" to variant_op...", cib_action);
return cib_internal_op(the_cib, cib_action, host, obj_type, input, output, call_options, cib_user);
} else {
crm_err("You must specify an operation");
}
return -EINVAL;
}
int
do_init(void)
{
int rc = pcmk_ok;
the_cib = cib_new();
rc = the_cib->cmds->signon(the_cib, crm_system_name, cib_command);
if (rc != pcmk_ok) {
crm_err("Could not connect to the CIB: %s", pcmk_strerror(rc));
fprintf(stderr, "Could not connect to the CIB: %s\n",
pcmk_strerror(rc));
}
return rc;
}
void
cibadmin_op_callback(xmlNode * msg, int call_id, int rc, xmlNode * output, void *user_data)
{
rc = pcmk_legacy2rc(rc);
exit_code = pcmk_rc2exitc(rc);
if (rc == pcmk_rc_schema_unchanged) {
report_schema_unchanged();
} else if (rc != pcmk_rc_ok) {
crm_warn("Call %s failed: %s " CRM_XS " rc=%d",
cib_action, pcmk_rc_str(rc), rc);
fprintf(stderr, "Call %s failed: %s\n", cib_action, pcmk_rc_str(rc));
print_xml_output(output);
} else if (pcmk__str_eq(cib_action, PCMK__CIB_REQUEST_QUERY, pcmk__str_none)
&& (output == NULL)) {
crm_err("Query returned no output");
crm_log_xml_err(msg, "no output");
} else if (output == NULL) {
crm_info("Call passed");
} else {
crm_info("Call passed");
print_xml_output(output);
}
if (call_id == request_id) {
g_main_loop_quit(mainloop);
} else {
crm_info("Message was not the response we were looking for (%d vs. %d)",
call_id, request_id);
}
}

File Metadata

Mime Type
text/x-diff
Expires
Tue, Jul 8, 6:01 PM (1 d, 1 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1984596
Default Alt Text
(127 KB)

Event Timeline