summaryrefslogtreecommitdiffstats
path: root/include/crm
diff options
context:
space:
mode:
Diffstat (limited to 'include/crm')
-rw-r--r--include/crm/Makefile.am22
-rw-r--r--include/crm/cib.h60
-rw-r--r--include/crm/cib/Makefile.am16
-rw-r--r--include/crm/cib/cib_types.h223
-rw-r--r--include/crm/cib/internal.h264
-rw-r--r--include/crm/cib/util.h73
-rw-r--r--include/crm/cib/util_compat.h40
-rw-r--r--include/crm/cluster.h236
-rw-r--r--include/crm/cluster/Makefile.am14
-rw-r--r--include/crm/cluster/compat.h37
-rw-r--r--include/crm/cluster/election_internal.h86
-rw-r--r--include/crm/cluster/internal.h133
-rw-r--r--include/crm/common/Makefile.am55
-rw-r--r--include/crm/common/acl.h38
-rw-r--r--include/crm/common/acl_internal.h32
-rw-r--r--include/crm/common/agents.h83
-rw-r--r--include/crm/common/agents_compat.h35
-rw-r--r--include/crm/common/alerts_internal.h92
-rw-r--r--include/crm/common/attrd_internal.h50
-rw-r--r--include/crm/common/cib.h27
-rw-r--r--include/crm/common/cmdline_internal.h186
-rw-r--r--include/crm/common/health_internal.h40
-rw-r--r--include/crm/common/internal.h358
-rw-r--r--include/crm/common/io_internal.h57
-rw-r--r--include/crm/common/ipc.h233
-rw-r--r--include/crm/common/ipc_attrd_internal.h198
-rw-r--r--include/crm/common/ipc_controld.h111
-rw-r--r--include/crm/common/ipc_internal.h293
-rw-r--r--include/crm/common/ipc_pacemakerd.h79
-rw-r--r--include/crm/common/ipc_schedulerd.h64
-rw-r--r--include/crm/common/iso8601.h130
-rw-r--r--include/crm/common/iso8601_internal.h42
-rw-r--r--include/crm/common/lists_internal.h36
-rw-r--r--include/crm/common/logging.h411
-rw-r--r--include/crm/common/logging_compat.h85
-rw-r--r--include/crm/common/logging_internal.h95
-rw-r--r--include/crm/common/mainloop.h192
-rw-r--r--include/crm/common/mainloop_compat.h36
-rw-r--r--include/crm/common/messages_internal.h122
-rw-r--r--include/crm/common/nvpair.h88
-rw-r--r--include/crm/common/options_internal.h118
-rw-r--r--include/crm/common/output.h83
-rw-r--r--include/crm/common/output_internal.h989
-rw-r--r--include/crm/common/remote_internal.h48
-rw-r--r--include/crm/common/results.h393
-rw-r--r--include/crm/common/results_compat.h35
-rw-r--r--include/crm/common/results_internal.h88
-rw-r--r--include/crm/common/strings_internal.h214
-rw-r--r--include/crm/common/unittest_internal.h84
-rw-r--r--include/crm/common/util.h153
-rw-r--r--include/crm/common/util_compat.h164
-rw-r--r--include/crm/common/xml.h306
-rw-r--r--include/crm/common/xml_compat.h65
-rw-r--r--include/crm/common/xml_internal.h414
-rw-r--r--include/crm/compatibility.h243
-rw-r--r--include/crm/crm.h238
-rw-r--r--include/crm/crm_compat.h61
-rw-r--r--include/crm/fencing/Makefile.am13
-rw-r--r--include/crm/fencing/internal.h222
-rw-r--r--include/crm/lrmd.h628
-rw-r--r--include/crm/lrmd_internal.h91
-rw-r--r--include/crm/msg_xml.h487
-rw-r--r--include/crm/msg_xml_compat.h65
-rw-r--r--include/crm/pengine/Makefile.am17
-rw-r--r--include/crm/pengine/common.h209
-rw-r--r--include/crm/pengine/common_compat.h37
-rw-r--r--include/crm/pengine/complex.h37
-rw-r--r--include/crm/pengine/internal.h724
-rw-r--r--include/crm/pengine/pe_types.h568
-rw-r--r--include/crm/pengine/pe_types_compat.h63
-rw-r--r--include/crm/pengine/remote_internal.h41
-rw-r--r--include/crm/pengine/rules.h80
-rw-r--r--include/crm/pengine/rules_compat.h72
-rw-r--r--include/crm/pengine/rules_internal.h36
-rw-r--r--include/crm/pengine/status.h112
-rw-r--r--include/crm/services.h432
-rw-r--r--include/crm/services_compat.h98
-rw-r--r--include/crm/services_internal.h62
-rw-r--r--include/crm/stonith-ng.h707
79 files changed, 12969 insertions, 0 deletions
diff --git a/include/crm/Makefile.am b/include/crm/Makefile.am
new file mode 100644
index 0000000..6dd52fd
--- /dev/null
+++ b/include/crm/Makefile.am
@@ -0,0 +1,22 @@
+#
+# Copyright 2004-2021 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.
+#
+
+MAINTAINERCLEANFILES = Makefile.in
+
+headerdir=$(pkgincludedir)/crm
+
+header_HEADERS = cib.h cluster.h compatibility.h crm.h \
+ lrmd.h msg_xml.h services.h stonith-ng.h \
+ crm_compat.h \
+ msg_xml_compat.h \
+ services_compat.h
+
+noinst_HEADERS = lrmd_internal.h services_internal.h
+
+SUBDIRS = common pengine cib fencing cluster
diff --git a/include/crm/cib.h b/include/crm/cib.h
new file mode 100644
index 0000000..a93bfde
--- /dev/null
+++ b/include/crm/cib.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2004-2019 the Pacemaker project contributors
+ *
+ * The version control history for this file may have further details.
+ *
+ * This source code is licensed under the GNU Lesser General Public License
+ * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
+ */
+
+#ifndef PCMK__CRM_CIB__H
+# define PCMK__CRM_CIB__H
+
+# include <glib.h> // gboolean
+# include <crm/common/ipc.h>
+# include <crm/common/xml.h>
+# include <crm/cib/cib_types.h>
+# include <crm/cib/util.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * \file
+ * \brief Cluster Configuration
+ * \ingroup cib
+ */
+
+# define CIB_FEATURE_SET "2.0"
+
+/* use compare_version() for doing comparisons */
+
+#define T_CIB_DIFF_NOTIFY "cib_diff_notify"
+
+/* Core functions */
+cib_t *cib_new(void);
+cib_t *cib_native_new(void);
+cib_t *cib_file_new(const char *filename);
+cib_t *cib_remote_new(const char *server, const char *user, const char *passwd, int port,
+ gboolean encrypted);
+
+cib_t *cib_new_no_shadow(void);
+char *get_shadow_file(const char *name);
+cib_t *cib_shadow_new(const char *name);
+
+void cib_free_notify(cib_t *cib);
+void cib_free_callbacks(cib_t *cib);
+void cib_delete(cib_t * cib);
+
+void cib_dump_pending_callbacks(void);
+int num_cib_op_callbacks(void);
+void remove_cib_op_callback(int call_id, gboolean all_callbacks);
+
+# define CIB_LIBRARY "libcib.so.27"
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/crm/cib/Makefile.am b/include/crm/cib/Makefile.am
new file mode 100644
index 0000000..0cd236c
--- /dev/null
+++ b/include/crm/cib/Makefile.am
@@ -0,0 +1,16 @@
+#
+# Copyright 2012-2021 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.
+#
+MAINTAINERCLEANFILES = Makefile.in
+
+headerdir=$(pkgincludedir)/crm/cib
+
+noinst_HEADERS = internal.h
+header_HEADERS = cib_types.h \
+ util.h \
+ util_compat.h
diff --git a/include/crm/cib/cib_types.h b/include/crm/cib/cib_types.h
new file mode 100644
index 0000000..5bd10e4
--- /dev/null
+++ b/include/crm/cib/cib_types.h
@@ -0,0 +1,223 @@
+/*
+ * Copyright 2004-2023 the Pacemaker project contributors
+ *
+ * The version control history for this file may have further details.
+ *
+ * This source code is licensed under the GNU Lesser General Public License
+ * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
+ */
+
+#ifndef PCMK__CRM_CIB_CIB_TYPES__H
+# define PCMK__CRM_CIB_CIB_TYPES__H
+
+# include <glib.h> // gboolean, GList
+# include <libxml/tree.h> // xmlNode
+# include <crm/common/ipc.h>
+# include <crm/common/xml.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * \file
+ * \brief Data types for Cluster Information Base access
+ * \ingroup cib
+ */
+
+enum cib_variant {
+ cib_undefined = 0,
+ cib_native = 1,
+ cib_file = 2,
+ cib_remote = 3,
+
+#if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1)
+ //! \deprecated This value will be removed in a future release
+ cib_database = 4,
+#endif // !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1)
+};
+
+enum cib_state {
+ cib_connected_command,
+ cib_connected_query,
+ cib_disconnected
+};
+
+enum cib_conn_type {
+ cib_command,
+ cib_query,
+ cib_no_connection,
+ cib_command_nonblocking,
+};
+
+enum cib_call_options {
+ cib_none = 0,
+ cib_verbose = (1 << 0), //!< Prefer stderr to logs
+ cib_xpath = (1 << 1),
+ cib_multiple = (1 << 2),
+ cib_can_create = (1 << 3),
+ cib_discard_reply = (1 << 4),
+ cib_no_children = (1 << 5),
+ cib_xpath_address = (1 << 6),
+ cib_mixed_update = (1 << 7),
+ cib_scope_local = (1 << 8),
+ cib_dryrun = (1 << 9),
+ cib_sync_call = (1 << 12),
+ cib_no_mtime = (1 << 13),
+ cib_zero_copy = (1 << 14),
+ cib_inhibit_notify = (1 << 16),
+
+#if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1)
+ //! \deprecated This value will be removed in a future release
+ cib_quorum_override = (1 << 20),
+#endif // !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1)
+
+ //! \deprecated This value will be removed in a future release
+ cib_inhibit_bcast = (1 << 24),
+
+ cib_force_diff = (1 << 28),
+};
+
+typedef struct cib_s cib_t;
+
+typedef struct cib_api_operations_s {
+ int (*signon) (cib_t *cib, const char *name, enum cib_conn_type type);
+ int (*signon_raw) (cib_t *cib, const char *name, enum cib_conn_type type,
+ int *event_fd);
+ int (*signoff) (cib_t *cib);
+ int (*free) (cib_t *cib);
+ int (*set_op_callback) (cib_t *cib, void (*callback) (const xmlNode *msg,
+ int callid, int rc,
+ xmlNode *output));
+ int (*add_notify_callback) (cib_t *cib, const char *event,
+ void (*callback) (const char *event,
+ xmlNode *msg));
+ int (*del_notify_callback) (cib_t *cib, const char *event,
+ void (*callback) (const char *event,
+ xmlNode *msg));
+ int (*set_connection_dnotify) (cib_t *cib,
+ void (*dnotify) (gpointer user_data));
+ int (*inputfd) (cib_t *cib);
+ int (*noop) (cib_t *cib, int call_options);
+ int (*ping) (cib_t *cib, xmlNode **output_data, int call_options);
+ int (*query) (cib_t *cib, const char *section, xmlNode **output_data,
+ int call_options);
+ int (*query_from) (cib_t *cib, const char *host, const char *section,
+ xmlNode **output_data, int call_options);
+
+ //! \deprecated This method will be removed and should not be used
+ int (*is_master) (cib_t *cib);
+
+ //! \deprecated Use the set_primary() method instead
+ int (*set_master) (cib_t *cib, int call_options);
+
+ //! \deprecated Use the set_secondary() method instead
+ int (*set_slave) (cib_t *cib, int call_options);
+
+ //! \deprecated This method will be removed and should not be used
+ int (*set_slave_all) (cib_t *cib, int call_options);
+
+ int (*sync) (cib_t *cib, const char *section, int call_options);
+ int (*sync_from) (cib_t *cib, const char *host, const char *section,
+ int call_options);
+ int (*upgrade) (cib_t *cib, int call_options);
+ int (*bump_epoch) (cib_t *cib, int call_options);
+ int (*create) (cib_t *cib, const char *section, xmlNode *data,
+ int call_options);
+ int (*modify) (cib_t *cib, const char *section, xmlNode *data,
+ int call_options);
+
+ //! \deprecated Use the \p modify() method instead
+ int (*update) (cib_t *cib, const char *section, xmlNode *data,
+ int call_options);
+
+ int (*replace) (cib_t *cib, const char *section, xmlNode *data,
+ int call_options);
+ int (*remove) (cib_t *cib, const char *section, xmlNode *data,
+ int call_options);
+ int (*erase) (cib_t *cib, xmlNode **output_data, int call_options);
+
+ //! \deprecated This method does nothing and should not be called
+ int (*delete_absolute) (cib_t *cib, const char *section, xmlNode *data,
+ int call_options);
+
+ int (*quit) (cib_t *cib, int call_options);
+ int (*register_notification) (cib_t *cib, const char *callback,
+ int enabled);
+ gboolean (*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 (*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 *));
+
+ /*!
+ * \brief Set the local CIB manager as the cluster's primary instance
+ *
+ * \param[in,out] cib CIB connection
+ * \param[in] call_options Group of enum cib_call_options flags
+ *
+ * \return Legacy Pacemaker return code (in particular, pcmk_ok on success)
+ */
+ int (*set_primary)(cib_t *cib, int call_options);
+
+ /*!
+ * \brief Set the local CIB manager as a secondary instance
+ *
+ * \param[in,out] cib CIB connection
+ * \param[in] call_options Group of enum cib_call_options flags
+ *
+ * \return Legacy Pacemaker return code (in particular, pcmk_ok on success)
+ */
+ int (*set_secondary)(cib_t *cib, int call_options);
+
+ /*!
+ * \brief Get the given CIB connection's unique client identifier(s)
+ *
+ * These can be used to check whether this client requested the action that
+ * triggered a CIB notification.
+ *
+ * \param[in] cib CIB connection
+ * \param[out] async_id If not \p NULL, where to store asynchronous client
+ * ID
+ * \param[out] sync_id If not \p NULL, where to store synchronous client
+ * ID
+ *
+ * \return Legacy Pacemaker return code
+ *
+ * \note The client IDs are assigned by \p pacemaker-based when the client
+ * connects. \p cib_t variants that don't connect to
+ * \p pacemaker-based may never be assigned a client ID.
+ * \note Some variants may have only one client for both asynchronous and
+ * synchronous requests.
+ */
+ int (*client_id)(const cib_t *cib, const char **async_id,
+ const char **sync_id);
+} cib_api_operations_t;
+
+struct cib_s {
+ enum cib_state state;
+ enum cib_conn_type type;
+ enum cib_variant variant;
+
+ int call_id;
+ int call_timeout;
+ void *variant_opaque;
+ void *delegate_fn;
+
+ GList *notify_list;
+ void (*op_callback) (const xmlNode *msg, int call_id, int rc,
+ xmlNode *output);
+ cib_api_operations_t *cmds;
+};
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // PCMK__CRM_CIB_CIB_TYPES__H
diff --git a/include/crm/cib/internal.h b/include/crm/cib/internal.h
new file mode 100644
index 0000000..374902b
--- /dev/null
+++ b/include/crm/cib/internal.h
@@ -0,0 +1,264 @@
+/*
+ * Copyright 2004-2023 the Pacemaker project contributors
+ *
+ * The version control history for this file may have further details.
+ *
+ * This source code is licensed under the GNU Lesser General Public License
+ * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
+ */
+
+#ifndef CIB_INTERNAL__H
+# define CIB_INTERNAL__H
+# include <crm/cib.h>
+# include <crm/common/ipc_internal.h>
+# include <crm/common/output_internal.h>
+
+// Request types for CIB manager IPC/CPG
+#define PCMK__CIB_REQUEST_SECONDARY "cib_slave"
+#define PCMK__CIB_REQUEST_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 PCMK__CIB_REQUEST_CREATE "cib_create"
+#define PCMK__CIB_REQUEST_MODIFY "cib_modify"
+#define PCMK__CIB_REQUEST_DELETE "cib_delete"
+#define PCMK__CIB_REQUEST_ERASE "cib_erase"
+#define PCMK__CIB_REQUEST_REPLACE "cib_replace"
+#define PCMK__CIB_REQUEST_APPLY_PATCH "cib_apply_diff"
+#define PCMK__CIB_REQUEST_UPGRADE "cib_upgrade"
+#define PCMK__CIB_REQUEST_ABS_DELETE "cib_delete_alt"
+#define PCMK__CIB_REQUEST_NOOP "noop"
+#define PCMK__CIB_REQUEST_SHUTDOWN "cib_shutdown_req"
+
+# define F_CIB_CLIENTID "cib_clientid"
+# define F_CIB_CALLOPTS "cib_callopt"
+# define F_CIB_CALLID "cib_callid"
+# define F_CIB_CALLDATA "cib_calldata"
+# define F_CIB_OPERATION "cib_op"
+# define F_CIB_ISREPLY "cib_isreplyto"
+# define F_CIB_SECTION "cib_section"
+# define F_CIB_HOST "cib_host"
+# define F_CIB_RC "cib_rc"
+# define F_CIB_UPGRADE_RC "cib_upgrade_rc"
+# define F_CIB_DELEGATED "cib_delegated_from"
+# define F_CIB_OBJID "cib_object"
+# define F_CIB_OBJTYPE "cib_object_type"
+# define F_CIB_EXISTING "cib_existing_object"
+# define F_CIB_SEENCOUNT "cib_seen"
+# define F_CIB_TIMEOUT "cib_timeout"
+# define F_CIB_UPDATE "cib_update"
+# define F_CIB_GLOBAL_UPDATE "cib_update"
+# define F_CIB_UPDATE_RESULT "cib_update_result"
+# define F_CIB_CLIENTNAME "cib_clientname"
+# define F_CIB_NOTIFY_TYPE "cib_notify_type"
+# define F_CIB_NOTIFY_ACTIVATE "cib_notify_activate"
+# define F_CIB_UPDATE_DIFF "cib_update_diff"
+# define F_CIB_USER "cib_user"
+# define F_CIB_LOCAL_NOTIFY_ID "cib_local_notify_id"
+# define F_CIB_PING_ID "cib_ping_id"
+# define F_CIB_SCHEMA_MAX "cib_schema_max"
+# define F_CIB_CHANGE_SECTION "cib_change_section"
+
+# define T_CIB "cib"
+# define T_CIB_NOTIFY "cib_notify"
+/* notify sub-types */
+# define T_CIB_PRE_NOTIFY "cib_pre_notify"
+# define T_CIB_POST_NOTIFY "cib_post_notify"
+# define T_CIB_UPDATE_CONFIRM "cib_update_confirmation"
+# define T_CIB_REPLACE_NOTIFY "cib_refresh_notify"
+
+/*!
+ * \internal
+ * \enum cib_change_section_info
+ * \brief Flags to indicate which sections of the CIB have changed
+ */
+enum cib_change_section_info {
+ cib_change_section_none = 0, //!< No sections have changed
+ cib_change_section_nodes = (1 << 0), //!< The nodes section has changed
+ cib_change_section_alerts = (1 << 1), //!< The alerts section has changed
+ cib_change_section_status = (1 << 2), //!< The status section has changed
+};
+
+
+gboolean cib_diff_version_details(xmlNode * diff, int *admin_epoch, int *epoch, int *updates,
+ int *_admin_epoch, int *_epoch, int *_updates);
+
+gboolean cib_read_config(GHashTable * options, xmlNode * current_cib);
+
+typedef 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 *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_process_query(const char *op, int options, const char *section, xmlNode * req,
+ xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib,
+ xmlNode ** answer);
+
+int cib_process_erase(const char *op, int options, const char *section, xmlNode * req,
+ xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib,
+ xmlNode ** answer);
+
+int cib_process_bump(const char *op, int options, const char *section, xmlNode * req,
+ xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib,
+ xmlNode ** answer);
+
+int cib_process_replace(const char *op, int options, const char *section, xmlNode * req,
+ xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib,
+ xmlNode ** answer);
+
+int cib_process_create(const char *op, int options, const char *section, xmlNode * req,
+ xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib,
+ xmlNode ** answer);
+
+int cib_process_modify(const char *op, int options, const char *section, xmlNode * req,
+ xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib,
+ xmlNode ** answer);
+
+int cib_process_delete(const char *op, int options, const char *section, xmlNode * req,
+ xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib,
+ xmlNode ** answer);
+
+int cib_process_diff(const char *op, int options, const char *section, xmlNode * req,
+ xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib,
+ xmlNode ** answer);
+
+int cib_process_upgrade(const char *op, int options, const char *section, xmlNode * req,
+ xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib,
+ xmlNode ** answer);
+
+/*!
+ * \internal
+ * \brief Query or modify a CIB
+ *
+ * \param[in] op PCMK__CIB_REQUEST_* operation to be performed
+ * \param[in] options Flag set of \c cib_call_options
+ * \param[in] section XPath to query or modify
+ * \param[in] req unused
+ * \param[in] input Portion of CIB to modify (used with
+ * PCMK__CIB_REQUEST_CREATE,
+ * PCMK__CIB_REQUEST_MODIFY, and
+ * PCMK__CIB_REQUEST_REPLACE)
+ * \param[in,out] existing_cib Input CIB (used with PCMK__CIB_REQUEST_QUERY)
+ * \param[in,out] result_cib CIB copy to make changes in (used with
+ * PCMK__CIB_REQUEST_CREATE,
+ * PCMK__CIB_REQUEST_MODIFY,
+ * PCMK__CIB_REQUEST_DELETE, and
+ * PCMK__CIB_REQUEST_REPLACE)
+ * \param[out] answer Query result (used with PCMK__CIB_REQUEST_QUERY)
+ *
+ * \return Legacy Pacemaker return code
+ */
+int cib_process_xpath(const char *op, int options, const char *section,
+ const xmlNode *req, xmlNode *input, xmlNode *existing_cib,
+ xmlNode **result_cib, xmlNode ** answer);
+
+bool cib__config_changed_v1(xmlNode *last, xmlNode *next, xmlNode **diff);
+
+int cib_internal_op(cib_t * cib, const char *op, const char *host,
+ const char *section, xmlNode * data,
+ xmlNode ** output_data, int call_options, const char *user_name);
+
+
+int cib_file_read_and_verify(const char *filename, const char *sigfile,
+ xmlNode **root);
+int cib_file_write_with_digest(xmlNode *cib_root, const char *cib_dirname,
+ const char *cib_filename);
+
+void cib__set_output(cib_t *cib, pcmk__output_t *out);
+
+cib_callback_client_t* cib__lookup_id (int call_id);
+
+/*!
+ * \internal
+ * \brief Connect to, query, and optionally disconnect from the CIB
+ *
+ * Open a read-write connection to the CIB manager if an already connected
+ * client is not passed in. Then query the CIB and store the resulting XML.
+ * Finally, disconnect if the CIB connection isn't being returned to the caller.
+ *
+ * \param[in,out] out Output object (may be \p NULL)
+ * \param[in,out] cib If not \p NULL, where to store CIB connection
+ * \param[out] cib_object Where to store query result
+ *
+ * \return Standard Pacemaker return code
+ *
+ * \note If \p cib is not \p NULL, the caller is responsible for freeing \p *cib
+ * using \p cib_delete().
+ * \note If \p *cib points to an existing \p cib_t object, this function will
+ * reuse it instead of creating a new one. If the existing client is
+ * already connected, the connection will be reused, even if it's
+ * read-only.
+ */
+int cib__signon_query(pcmk__output_t *out, cib_t **cib, xmlNode **cib_object);
+
+int cib__clean_up_connection(cib_t **cib);
+
+int cib__update_node_attr(pcmk__output_t *out, cib_t *cib, int call_options,
+ const char *section, const char *node_uuid, const char *set_type,
+ const char *set_name, const char *attr_id, const char *attr_name,
+ const char *attr_value, const char *user_name,
+ const char *node_type);
+
+int cib__get_node_attrs(pcmk__output_t *out, cib_t *cib, const char *section,
+ const char *node_uuid, const char *set_type, const char *set_name,
+ const char *attr_id, const char *attr_name, const char *user_name,
+ xmlNode **result);
+
+int cib__delete_node_attr(pcmk__output_t *out, cib_t *cib, int options,
+ const char *section, const char *node_uuid, const char *set_type,
+ const char *set_name, const char *attr_id, const char *attr_name,
+ const char *attr_value, const char *user_name);
+
+#endif
diff --git a/include/crm/cib/util.h b/include/crm/cib/util.h
new file mode 100644
index 0000000..18726bb
--- /dev/null
+++ b/include/crm/cib/util.h
@@ -0,0 +1,73 @@
+/*
+ * 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 PCMK__CRM_CIB_UTIL__H
+# define PCMK__CRM_CIB_UTIL__H
+
+#include <glib.h> // gboolean
+#include <libxml/tree.h> // xmlNode
+#include <crm/cib/cib_types.h> // cib_t
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Utility functions */
+xmlNode *createEmptyCib(int cib_epoch);
+
+gboolean cib_version_details(xmlNode * cib, int *admin_epoch, int *epoch, int *updates);
+
+int update_attr_delegate(cib_t * the_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, gboolean to_console,
+ const char *user_name, const char *node_type);
+
+int find_nvpair_attr_delegate(cib_t * the_cib, const char *attr,
+ const char *section, const char *node_uuid,
+ const char *set_type, const char *set_name,
+ const char *attr_id, const char *attr_name,
+ gboolean to_console, char **value, const char *user_name);
+
+int read_attr_delegate(cib_t * the_cib,
+ const char *section, const char *node_uuid,
+ const char *set_type, const char *set_name,
+ const char *attr_id, const char *attr_name,
+ char **attr_value, gboolean to_console, const char *user_name);
+
+int delete_attr_delegate(cib_t * the_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, gboolean to_console, const char *user_name);
+
+int query_node_uuid(cib_t * the_cib, const char *uname, char **uuid, int *is_remote_node);
+
+int query_node_uname(cib_t * the_cib, const char *uuid, char **uname);
+
+int set_standby(cib_t * the_cib, const char *uuid, const char *scope, const char *standby_value);
+
+xmlNode *cib_get_generation(cib_t * cib);
+
+void cib_metadata(void);
+const char *cib_pref(GHashTable * options, const char *name);
+
+int cib_apply_patch_event(xmlNode *event, xmlNode *input, xmlNode **output,
+ int level);
+
+#if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1)
+#include <crm/cib/util_compat.h>
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/crm/cib/util_compat.h b/include/crm/cib/util_compat.h
new file mode 100644
index 0000000..20f1e2d
--- /dev/null
+++ b/include/crm/cib/util_compat.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2021 the Pacemaker project contributors
+ *
+ * The version control history for this file may have further details.
+ *
+ * This source code is licensed under the GNU Lesser General Public License
+ * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
+ */
+
+#ifndef PCMK__CRM_CIB_UTIL_COMPAT__H
+# define PCMK__CRM_CIB_UTIL_COMPAT__H
+
+#include <crm/common/xml.h>
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * \file
+ * \brief Deprecated Pacemaker configuration utilities
+ * \ingroup cib
+ * \deprecated Do not include this header directly. The utilities in this
+ * header, and the header itself, will be removed in a future
+ * release.
+ */
+
+//! \deprecated Use pcmk_cib_xpath_for() instead
+const char *get_object_path(const char *object_type);
+
+//! \deprecated Use pcmk_cib_parent_name_for() instead
+const char *get_object_parent(const char *object_type);
+
+//! \deprecated Use pcmk_cib_xpath_for() instead
+xmlNode *get_object_root(const char *object_type, xmlNode *the_root);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // PCMK__CRM_CIB_UTIL_COMPAT__H
diff --git a/include/crm/cluster.h b/include/crm/cluster.h
new file mode 100644
index 0000000..bceb9c2
--- /dev/null
+++ b/include/crm/cluster.h
@@ -0,0 +1,236 @@
+/*
+ * Copyright 2004-2023 the Pacemaker project contributors
+ *
+ * The version control history for this file may have further details.
+ *
+ * This source code is licensed under the GNU Lesser General Public License
+ * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
+ */
+
+#ifndef PCMK__CRM_CLUSTER__H
+# define PCMK__CRM_CLUSTER__H
+
+# include <stdint.h> // uint32_t, uint64_t
+# include <glib.h> // gboolean, GHashTable
+# include <libxml/tree.h> // xmlNode
+# include <crm/common/xml.h>
+# include <crm/common/util.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+# if SUPPORT_COROSYNC
+# include <corosync/cpg.h>
+# endif
+
+extern gboolean crm_have_quorum;
+extern GHashTable *crm_peer_cache;
+extern GHashTable *crm_remote_peer_cache;
+extern unsigned long long crm_peer_seq;
+
+#define CRM_NODE_LOST "lost"
+#define CRM_NODE_MEMBER "member"
+
+enum crm_join_phase {
+ /* @COMPAT: crm_join_nack_quiet can be replaced by crm_node_t:user_data
+ * at a compatibility break.
+ */
+ //! Not allowed to join, but don't send a nack message
+ crm_join_nack_quiet = -2,
+
+ crm_join_nack = -1,
+ crm_join_none = 0,
+ crm_join_welcomed = 1,
+ crm_join_integrated = 2,
+ crm_join_finalized = 3,
+ crm_join_confirmed = 4,
+};
+
+enum crm_node_flags {
+ /* node is not a cluster node and should not be considered for cluster membership */
+ crm_remote_node = 0x0001,
+
+ /* node's cache entry is dirty */
+ crm_node_dirty = 0x0010,
+};
+
+typedef struct crm_peer_node_s {
+ char *uname; // Node name as known to cluster
+ char *uuid; // Node UUID to ensure uniqueness
+ char *state; // @TODO change to enum
+ uint64_t flags; // Bitmask of crm_node_flags
+ uint64_t last_seen; // Only needed by cluster nodes
+ uint32_t processes; // @TODO most not needed, merge into flags
+
+ /* @TODO When we can break public API compatibility, we can make the rest of
+ * these members separate structs and use void *cluster_data and
+ * void *user_data here instead, to abstract the cluster layer further.
+ */
+
+ // Currently only needed by corosync stack
+ uint32_t id; // Node ID
+ time_t when_lost; // When CPG membership was last lost
+
+ // Only used by controller
+ enum crm_join_phase join;
+ char *expected;
+
+ time_t peer_lost;
+ char *conn_host;
+} crm_node_t;
+
+void crm_peer_init(void);
+void crm_peer_destroy(void);
+
+typedef struct crm_cluster_s {
+ char *uuid;
+ char *uname;
+ uint32_t nodeid;
+
+ void (*destroy) (gpointer);
+
+# if SUPPORT_COROSYNC
+ /* @TODO When we can break public API compatibility, make these members a
+ * separate struct and use void *cluster_data here instead, to abstract the
+ * cluster layer further.
+ */
+ struct cpg_name group;
+ cpg_callbacks_t cpg;
+ cpg_handle_t cpg_handle;
+# endif
+
+} crm_cluster_t;
+
+gboolean crm_cluster_connect(crm_cluster_t *cluster);
+void crm_cluster_disconnect(crm_cluster_t *cluster);
+
+crm_cluster_t *pcmk_cluster_new(void);
+void pcmk_cluster_free(crm_cluster_t *cluster);
+
+enum crm_ais_msg_class {
+ crm_class_cluster = 0,
+};
+
+enum crm_ais_msg_types {
+ crm_msg_none = 0,
+ crm_msg_ais = 1,
+ crm_msg_lrmd = 2,
+ crm_msg_cib = 3,
+ crm_msg_crmd = 4,
+ crm_msg_attrd = 5,
+ crm_msg_stonithd = 6,
+ crm_msg_te = 7,
+ crm_msg_pe = 8,
+ crm_msg_stonith_ng = 9,
+};
+
+/* used with crm_get_peer_full */
+enum crm_get_peer_flags {
+ CRM_GET_PEER_CLUSTER = 0x0001,
+ CRM_GET_PEER_REMOTE = 0x0002,
+ CRM_GET_PEER_ANY = CRM_GET_PEER_CLUSTER|CRM_GET_PEER_REMOTE,
+};
+
+gboolean send_cluster_message(const crm_node_t *node,
+ enum crm_ais_msg_types service, xmlNode *data,
+ gboolean ordered);
+
+int crm_remote_peer_cache_size(void);
+
+/* Initialize and refresh the remote peer cache from a cib config */
+void crm_remote_peer_cache_refresh(xmlNode *cib);
+crm_node_t *crm_remote_peer_get(const char *node_name);
+void crm_remote_peer_cache_remove(const char *node_name);
+
+/* allows filtering of remote and cluster nodes using crm_get_peer_flags */
+crm_node_t *crm_get_peer_full(unsigned int id, const char *uname, int flags);
+
+/* only searches cluster nodes */
+crm_node_t *crm_get_peer(unsigned int id, const char *uname);
+
+guint crm_active_peers(void);
+gboolean crm_is_peer_active(const crm_node_t * node);
+guint reap_crm_member(uint32_t id, const char *name);
+
+# if SUPPORT_COROSYNC
+uint32_t get_local_nodeid(cpg_handle_t handle);
+
+gboolean cluster_connect_cpg(crm_cluster_t *cluster);
+void cluster_disconnect_cpg(crm_cluster_t * cluster);
+
+void pcmk_cpg_membership(cpg_handle_t handle,
+ const struct cpg_name *groupName,
+ const struct cpg_address *member_list, size_t member_list_entries,
+ const struct cpg_address *left_list, size_t left_list_entries,
+ const struct cpg_address *joined_list, size_t joined_list_entries);
+gboolean crm_is_corosync_peer_active(const crm_node_t * node);
+gboolean send_cluster_text(enum crm_ais_msg_class msg_class, const char *data,
+ gboolean local, const crm_node_t *node,
+ enum crm_ais_msg_types dest);
+char *pcmk_message_common_cs(cpg_handle_t handle, uint32_t nodeid, uint32_t pid, void *msg,
+ uint32_t *kind, const char **from);
+# endif
+
+const char *crm_peer_uuid(crm_node_t *node);
+const char *crm_peer_uname(const char *uuid);
+void set_uuid(xmlNode *xml, const char *attr, crm_node_t *node);
+
+enum crm_status_type {
+ crm_status_uname,
+ crm_status_nstate,
+ crm_status_processes,
+};
+
+enum crm_ais_msg_types text2msg_type(const char *text);
+void crm_set_status_callback(void (*dispatch) (enum crm_status_type, crm_node_t *, const void *));
+void crm_set_autoreap(gboolean autoreap);
+
+enum cluster_type_e {
+ pcmk_cluster_unknown = 0x0001,
+ pcmk_cluster_invalid = 0x0002,
+ // 0x0004 was heartbeat
+ // 0x0010 was corosync 1 with plugin
+ pcmk_cluster_corosync = 0x0020,
+ // 0x0040 was corosync 1 with CMAN
+};
+
+enum cluster_type_e get_cluster_type(void);
+const char *name_for_cluster_type(enum cluster_type_e type);
+
+gboolean is_corosync_cluster(void);
+
+const char *get_local_node_name(void);
+char *get_node_name(uint32_t nodeid);
+
+/*!
+ * \brief Get log-friendly string equivalent of a join phase
+ *
+ * \param[in] phase Join phase
+ *
+ * \return Log-friendly string equivalent of \p phase
+ */
+static inline const char *
+crm_join_phase_str(enum crm_join_phase phase)
+{
+ switch (phase) {
+ case crm_join_nack_quiet: return "nack_quiet";
+ case crm_join_nack: return "nack";
+ case crm_join_none: return "none";
+ case crm_join_welcomed: return "welcomed";
+ case crm_join_integrated: return "integrated";
+ case crm_join_finalized: return "finalized";
+ case crm_join_confirmed: return "confirmed";
+ default: return "invalid";
+ }
+}
+
+#if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1)
+#include <crm/cluster/compat.h>
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/crm/cluster/Makefile.am b/include/crm/cluster/Makefile.am
new file mode 100644
index 0000000..96f2bd0
--- /dev/null
+++ b/include/crm/cluster/Makefile.am
@@ -0,0 +1,14 @@
+#
+# Copyright 2012-2021 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.
+#
+MAINTAINERCLEANFILES = Makefile.in
+
+headerdir=$(pkgincludedir)/crm/cluster
+
+noinst_HEADERS = internal.h election_internal.h
+header_HEADERS = compat.h
diff --git a/include/crm/cluster/compat.h b/include/crm/cluster/compat.h
new file mode 100644
index 0000000..9bf14ee
--- /dev/null
+++ b/include/crm/cluster/compat.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2004-2021 the Pacemaker project contributors
+ *
+ * The version control history for this file may have further details.
+ *
+ * This source code is licensed under the GNU Lesser General Public License
+ * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
+ */
+
+#ifndef PCMK__CRM_CLUSTER_COMPAT__H
+# define PCMK__CRM_CLUSTER_COMPAT__H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * \file
+ * \brief Deprecated Pacemaker cluster API
+ * \ingroup cluster
+ * \deprecated Do not include this header directly. The cluster APIs in this
+ * header, and the header itself, will be removed in a future
+ * release.
+ */
+
+// \deprecated Use stonith_api_kick() from libstonithd instead
+int crm_terminate_member(int nodeid, const char *uname, void *unused);
+
+// \deprecated Use stonith_api_kick() from libstonithd instead
+int crm_terminate_member_no_mainloop(int nodeid, const char *uname,
+ int *connection);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // PCMK_CLUSTER_COMPAT__H
diff --git a/include/crm/cluster/election_internal.h b/include/crm/cluster/election_internal.h
new file mode 100644
index 0000000..665c3a0
--- /dev/null
+++ b/include/crm/cluster/election_internal.h
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2009-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 CRM_COMMON_ELECTION__H
+# define CRM_COMMON_ELECTION__H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * \file
+ * \brief Functions for conducting elections
+ *
+ * An election is useful for a daemon that runs on all nodes but needs any one
+ * instance to perform a special role.
+ *
+ * Elections are closely tied to the cluster peer cache. Peers in the cache that
+ * are active members are eligible to vote. Elections are named for logging
+ * purposes, but only one election may exist at any time, so typically an
+ * election would be created at daemon start-up and freed at shutdown.
+ *
+ * Pacemaker's election procedure has been heavily adapted from the
+ * Invitation Algorithm variant of the Garcia-Molina Bully Algorithm:
+ *
+ * https://en.wikipedia.org/wiki/Bully_algorithm
+ *
+ * Elections are conducted via cluster messages. There are two types of
+ * messages: a "vote" is a declaration of the voting node's candidacy, and is
+ * always broadcast; a "no-vote" is a concession by the responding node, and is
+ * always a reply to the preferred node's vote. (These correspond to "invite"
+ * and "accept" in the traditional algorithm.)
+ *
+ * A vote together with any no-vote replies to it is considered an election
+ * round. Rounds are numbered with a simple counter unique to each node
+ * (this would be the group number in the traditional algorithm). Concurrent
+ * election rounds are possible.
+ *
+ * An election round is started when any node broadcasts a vote. When a node
+ * receives another node's vote, it compares itself against the sending node
+ * according to certain metrics, and either starts a new round (if it prefers
+ * itself) or replies to the other node with a no-vote (if it prefers that
+ * node).
+ *
+ * If a node receives no-votes from all other active nodes, it declares itself
+ * the winner. The library API does not notify other nodes of this; callers
+ * must implement that if desired.
+ */
+
+typedef struct election_s election_t;
+
+/*! Possible election states */
+enum election_result {
+ election_start = 0, /*! new election needed */
+ election_in_progress, /*! election started but not all peers have voted */
+ election_lost, /*! local node lost most recent election */
+ election_won, /*! local node won most recent election */
+ election_error, /*! election message or election object invalid */
+};
+
+void election_fini(election_t *e);
+void election_reset(election_t *e);
+election_t *election_init(const char *name, const char *uname, guint period_ms, GSourceFunc cb);
+
+void election_timeout_set_period(election_t *e, guint period_ms);
+void election_timeout_stop(election_t *e);
+
+void election_vote(election_t *e);
+bool election_check(election_t *e);
+void election_remove(election_t *e, const char *uname);
+enum election_result election_state(const election_t *e);
+enum election_result election_count_vote(election_t *e, const xmlNode *message,
+ bool can_win);
+void election_clear_dampening(election_t *e);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/crm/cluster/internal.h b/include/crm/cluster/internal.h
new file mode 100644
index 0000000..9bc57c6
--- /dev/null
+++ b/include/crm/cluster/internal.h
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2004-2021 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 CRM_CLUSTER_INTERNAL__H
+# define CRM_CLUSTER_INTERNAL__H
+
+# include <stdint.h> // uint32_t, uint64_t
+# include <crm/cluster.h>
+
+/* *INDENT-OFF* */
+enum crm_proc_flag {
+ crm_proc_none = 0x00000001,
+
+ // Cluster layers
+ crm_proc_cpg = 0x04000000,
+
+ // Daemons
+ crm_proc_execd = 0x00000010,
+ crm_proc_based = 0x00000100,
+ crm_proc_controld = 0x00000200,
+ crm_proc_attrd = 0x00001000,
+ crm_proc_schedulerd = 0x00010000,
+ crm_proc_fenced = 0x00100000,
+};
+/* *INDENT-ON* */
+
+/*!
+ * \internal
+ * \brief Return the process bit corresponding to the current cluster stack
+ *
+ * \return Process flag if detectable, otherwise 0
+ */
+static inline uint32_t
+crm_get_cluster_proc(void)
+{
+ switch (get_cluster_type()) {
+ case pcmk_cluster_corosync:
+ return crm_proc_cpg;
+
+ default:
+ break;
+ }
+ return crm_proc_none;
+}
+
+/*!
+ * \internal
+ * \brief Get log-friendly string description of a Corosync return code
+ *
+ * \param[in] error Corosync return code
+ *
+ * \return Log-friendly string description corresponding to \p error
+ */
+static inline const char *
+pcmk__cs_err_str(int error)
+{
+# if SUPPORT_COROSYNC
+ switch (error) {
+ case CS_OK: return "OK";
+ case CS_ERR_LIBRARY: return "Library error";
+ case CS_ERR_VERSION: return "Version error";
+ case CS_ERR_INIT: return "Initialization error";
+ case CS_ERR_TIMEOUT: return "Timeout";
+ case CS_ERR_TRY_AGAIN: return "Try again";
+ case CS_ERR_INVALID_PARAM: return "Invalid parameter";
+ case CS_ERR_NO_MEMORY: return "No memory";
+ case CS_ERR_BAD_HANDLE: return "Bad handle";
+ case CS_ERR_BUSY: return "Busy";
+ case CS_ERR_ACCESS: return "Access error";
+ case CS_ERR_NOT_EXIST: return "Doesn't exist";
+ case CS_ERR_NAME_TOO_LONG: return "Name too long";
+ case CS_ERR_EXIST: return "Exists";
+ case CS_ERR_NO_SPACE: return "No space";
+ case CS_ERR_INTERRUPT: return "Interrupt";
+ case CS_ERR_NAME_NOT_FOUND: return "Name not found";
+ case CS_ERR_NO_RESOURCES: return "No resources";
+ case CS_ERR_NOT_SUPPORTED: return "Not supported";
+ case CS_ERR_BAD_OPERATION: return "Bad operation";
+ case CS_ERR_FAILED_OPERATION: return "Failed operation";
+ case CS_ERR_MESSAGE_ERROR: return "Message error";
+ case CS_ERR_QUEUE_FULL: return "Queue full";
+ case CS_ERR_QUEUE_NOT_AVAILABLE: return "Queue not available";
+ case CS_ERR_BAD_FLAGS: return "Bad flags";
+ case CS_ERR_TOO_BIG: return "Too big";
+ case CS_ERR_NO_SECTIONS: return "No sections";
+ }
+# endif
+ return "Corosync error";
+}
+
+# if SUPPORT_COROSYNC
+
+#if 0
+/* This is the new way to do it, but we still support all Corosync 2 versions,
+ * and this isn't always available. A better alternative here would be to check
+ * for support in the configure script and enable this conditionally.
+ */
+#define pcmk__init_cmap(handle) cmap_initialize_map((handle), CMAP_MAP_ICMAP)
+#else
+#define pcmk__init_cmap(handle) cmap_initialize(handle)
+#endif
+
+char *pcmk__corosync_cluster_name(void);
+bool pcmk__corosync_add_nodes(xmlNode *xml_parent);
+# endif
+
+crm_node_t *crm_update_peer_proc(const char *source, crm_node_t * peer,
+ uint32_t flag, const char *status);
+crm_node_t *pcmk__update_peer_state(const char *source, crm_node_t *node,
+ const char *state, uint64_t membership);
+
+void pcmk__update_peer_expected(const char *source, crm_node_t *node,
+ const char *expected);
+void pcmk__reap_unseen_nodes(uint64_t ring_id);
+
+void pcmk__corosync_quorum_connect(gboolean (*dispatch)(unsigned long long,
+ gboolean),
+ void (*destroy) (gpointer));
+crm_node_t *pcmk__search_node_caches(unsigned int id, const char *uname,
+ uint32_t flags);
+crm_node_t *pcmk__search_cluster_node_cache(unsigned int id, const char *uname);
+
+void pcmk__refresh_node_caches_from_cib(xmlNode *cib);
+crm_node_t *pcmk__search_known_node_cache(unsigned int id, const char *uname,
+ uint32_t flags);
+
+#endif
diff --git a/include/crm/common/Makefile.am b/include/crm/common/Makefile.am
new file mode 100644
index 0000000..7d417e4
--- /dev/null
+++ b/include/crm/common/Makefile.am
@@ -0,0 +1,55 @@
+#
+# 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.
+#
+
+MAINTAINERCLEANFILES = Makefile.in
+
+headerdir=$(pkgincludedir)/crm/common
+
+header_HEADERS = acl.h \
+ agents.h \
+ agents_compat.h \
+ cib.h \
+ ipc.h \
+ ipc_attrd_internal.h \
+ ipc_controld.h \
+ ipc_pacemakerd.h \
+ ipc_schedulerd.h \
+ iso8601.h \
+ logging.h \
+ logging_compat.h \
+ mainloop.h \
+ mainloop_compat.h \
+ nvpair.h \
+ output.h \
+ results.h \
+ results_compat.h \
+ util.h \
+ util_compat.h \
+ xml.h \
+ xml_compat.h
+
+noinst_HEADERS = acl_internal.h \
+ alerts_internal.h \
+ attrd_internal.h \
+ cmdline_internal.h \
+ health_internal.h \
+ internal.h \
+ io_internal.h \
+ ipc_internal.h \
+ iso8601_internal.h \
+ lists_internal.h \
+ logging_internal.h \
+ messages_internal.h \
+ options_internal.h \
+ output_internal.h \
+ remote_internal.h \
+ results_internal.h \
+ strings_internal.h \
+ unittest_internal.h \
+ xml_internal.h
diff --git a/include/crm/common/acl.h b/include/crm/common/acl.h
new file mode 100644
index 0000000..655ad55
--- /dev/null
+++ b/include/crm/common/acl.h
@@ -0,0 +1,38 @@
+/*
+ * 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 PCMK__CRM_COMMON_ACL__H
+# define PCMK__CRM_COMMON_ACL__H
+
+# include <libxml/tree.h> // xmlNode
+# include <stdbool.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * \file
+ * \brief Low-level API for XML Access Control Lists (ACLs)
+ * \ingroup core
+ */
+
+bool xml_acl_enabled(const xmlNode *xml);
+void xml_acl_disable(xmlNode *xml);
+bool xml_acl_denied(const xmlNode *xml);
+bool xml_acl_filtered_copy(const char *user, xmlNode* acl_source, xmlNode *xml,
+ xmlNode **result);
+
+bool pcmk_acl_required(const char *user);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // PCMK__CRM_COMMON_ACL__H
diff --git a/include/crm/common/acl_internal.h b/include/crm/common/acl_internal.h
new file mode 100644
index 0000000..ca67d68
--- /dev/null
+++ b/include/crm/common/acl_internal.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2015-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 CRM_COMMON_ACL_INTERNAL__H
+#define CRM_COMMON_ACL_INTERNAL__H
+
+#include <string.h> // strcmp()
+
+/* internal ACL-related utilities */
+
+char *pcmk__uid2username(uid_t uid);
+const char *pcmk__update_acl_user(xmlNode *request, const char *field,
+ const char *peer_user);
+
+static inline bool
+pcmk__is_privileged(const char *user)
+{
+ return user && (!strcmp(user, CRM_DAEMON_USER) || !strcmp(user, "root"));
+}
+
+void pcmk__enable_acl(xmlNode *acl_source, xmlNode *target, const char *user);
+
+bool pcmk__check_acl(xmlNode *xml, const char *name,
+ enum xml_private_flags mode);
+
+#endif /* CRM_COMMON_INTERNAL__H */
diff --git a/include/crm/common/agents.h b/include/crm/common/agents.h
new file mode 100644
index 0000000..5a67a87
--- /dev/null
+++ b/include/crm/common/agents.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2017-2023 the Pacemaker project contributors
+ *
+ * The version control history for this file may have further details.
+ *
+ * This source code is licensed under the GNU Lesser General Public License
+ * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
+ */
+
+#ifndef PCMK__CRM_COMMON_AGENTS__H
+# define PCMK__CRM_COMMON_AGENTS__H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * \file
+ * \brief API related to resource agents
+ * \ingroup core
+ */
+
+#include <stdint.h> // uint32_t
+#include <stdbool.h>
+
+// Known resource classes
+#define PCMK_RESOURCE_CLASS_OCF "ocf"
+#define PCMK_RESOURCE_CLASS_SERVICE "service"
+#define PCMK_RESOURCE_CLASS_LSB "lsb"
+#define PCMK_RESOURCE_CLASS_SYSTEMD "systemd"
+#define PCMK_RESOURCE_CLASS_STONITH "stonith"
+#define PCMK_RESOURCE_CLASS_ALERT "alert"
+//! \deprecated Do not use
+#define PCMK_RESOURCE_CLASS_NAGIOS "nagios"
+//! \deprecated Do not use
+#define PCMK_RESOURCE_CLASS_UPSTART "upstart"
+
+/* Special stonith-class agent parameters interpreted directly by Pacemaker
+ * (not including the pcmk_ACTION_{action,retries,timeout} parameters)
+ */
+#define PCMK_STONITH_ACTION_LIMIT "pcmk_action_limit"
+#define PCMK_STONITH_DELAY_BASE "pcmk_delay_base"
+#define PCMK_STONITH_DELAY_MAX "pcmk_delay_max"
+#define PCMK_STONITH_HOST_ARGUMENT "pcmk_host_argument"
+#define PCMK_STONITH_HOST_CHECK "pcmk_host_check"
+#define PCMK_STONITH_HOST_LIST "pcmk_host_list"
+#define PCMK_STONITH_HOST_MAP "pcmk_host_map"
+#define PCMK_STONITH_PROVIDES "provides"
+#define PCMK_STONITH_STONITH_TIMEOUT "stonith-timeout"
+
+// OCF Resource Agent API standard version that this Pacemaker supports
+#define PCMK_OCF_MAJOR_VERSION "1"
+#define PCMK_OCF_MINOR_VERSION "1"
+#define PCMK_OCF_VERSION PCMK_OCF_MAJOR_VERSION "." PCMK_OCF_MINOR_VERSION
+
+// Capabilities supported by a resource agent standard
+enum pcmk_ra_caps {
+ pcmk_ra_cap_none = 0,
+ pcmk_ra_cap_provider = (1 << 0), // Requires provider
+ pcmk_ra_cap_status = (1 << 1), // Supports status instead of monitor
+ pcmk_ra_cap_params = (1 << 2), // Supports parameters
+ pcmk_ra_cap_unique = (1 << 3), // Supports unique clones
+ pcmk_ra_cap_promotable = (1 << 4), // Supports promotable clones
+ pcmk_ra_cap_stdin = (1 << 5), // Reads from standard input
+ pcmk_ra_cap_fence_params = (1 << 6), // Supports pcmk_monitor_timeout, etc.
+};
+
+uint32_t pcmk_get_ra_caps(const char *standard);
+char *crm_generate_ra_key(const char *standard, const char *provider,
+ const char *type);
+int crm_parse_agent_spec(const char *spec, char **standard, char **provider,
+ char **type);
+bool pcmk_stonith_param(const char *param);
+
+#if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1)
+#include <crm/common/agents_compat.h>
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // PCMK__CRM_COMMON_AGENTS__H
diff --git a/include/crm/common/agents_compat.h b/include/crm/common/agents_compat.h
new file mode 100644
index 0000000..05a80f1
--- /dev/null
+++ b/include/crm/common/agents_compat.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2017-2021 the Pacemaker project contributors
+ *
+ * The version control history for this file may have further details.
+ *
+ * This source code is licensed under the GNU Lesser General Public License
+ * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
+ */
+
+#ifndef PCMK__CRM_COMMON_AGENTS_COMPAT__H
+# define PCMK__CRM_COMMON_AGENTS_COMPAT__H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * \file
+ * \brief Deprecated Pacemaker resource agent API
+ * \ingroup core
+ * \deprecated Do not include this header directly. The agent APIs in this
+ * header, and the header itself, will be removed in a future
+ * release.
+ */
+
+#include <stdbool.h>
+
+//! \deprecated Use pcmk_get_ra_caps() instead
+bool crm_provider_required(const char *standard);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // PCMK__CRM_COMMON_AGENTS_COMPAT__H
diff --git a/include/crm/common/alerts_internal.h b/include/crm/common/alerts_internal.h
new file mode 100644
index 0000000..ef64fab
--- /dev/null
+++ b/include/crm/common/alerts_internal.h
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2015-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 PCMK__ALERT_INTERNAL__H
+#define PCMK__ALERT_INTERNAL__H
+
+#include <glib.h>
+#include <stdbool.h>
+
+/* Default-Timeout to use before killing a alerts script (in milliseconds) */
+# define PCMK__ALERT_DEFAULT_TIMEOUT_MS (30000)
+
+/* Default-Format-String used to pass timestamps to the alerts scripts */
+# define PCMK__ALERT_DEFAULT_TSTAMP_FORMAT "%H:%M:%S.%06N"
+
+enum pcmk__alert_flags {
+ pcmk__alert_none = 0,
+ pcmk__alert_node = (1 << 0),
+ pcmk__alert_fencing = (1 << 1),
+ pcmk__alert_resource = (1 << 2),
+ pcmk__alert_attribute = (1 << 3),
+ pcmk__alert_default = pcmk__alert_node|pcmk__alert_fencing|
+ pcmk__alert_resource,
+};
+
+typedef struct {
+ char *id;
+ char *path;
+ char *tstamp_format;
+ char *recipient;
+ char **select_attribute_name;
+ GHashTable *envvars;
+ int timeout;
+ uint32_t flags;
+} pcmk__alert_t;
+
+enum pcmk__alert_keys_e {
+ PCMK__alert_key_recipient = 0,
+ PCMK__alert_key_node,
+ PCMK__alert_key_nodeid,
+ PCMK__alert_key_rsc,
+ PCMK__alert_key_task,
+ PCMK__alert_key_interval,
+ PCMK__alert_key_desc,
+ PCMK__alert_key_status,
+ PCMK__alert_key_target_rc,
+ PCMK__alert_key_rc,
+ PCMK__alert_key_kind,
+ PCMK__alert_key_version,
+ PCMK__alert_key_node_sequence,
+ PCMK__alert_key_timestamp,
+ PCMK__alert_key_attribute_name,
+ PCMK__alert_key_attribute_value,
+ PCMK__alert_key_timestamp_epoch,
+ PCMK__alert_key_timestamp_usec,
+ PCMK__alert_key_exec_time,
+ PCMK__alert_key_select_kind,
+ PCMK__alert_key_select_attribute_name
+};
+
+#define PCMK__ALERT_INTERNAL_KEY_MAX 19
+#define PCMK__ALERT_NODE_SEQUENCE "CRM_alert_node_sequence"
+
+extern const char *pcmk__alert_keys[PCMK__ALERT_INTERNAL_KEY_MAX][3];
+
+pcmk__alert_t *pcmk__dup_alert(const pcmk__alert_t *entry);
+pcmk__alert_t *pcmk__alert_new(const char *id, const char *path);
+void pcmk__free_alert(pcmk__alert_t *entry);
+void pcmk__add_alert_key(GHashTable *table, enum pcmk__alert_keys_e name,
+ const char *value);
+void pcmk__add_alert_key_int(GHashTable *table, enum pcmk__alert_keys_e name,
+ int value);
+bool pcmk__alert_in_patchset(xmlNode *msg, bool config);
+
+static inline const char *
+pcmk__alert_flag2text(enum pcmk__alert_flags flag)
+{
+ switch (flag) {
+ case pcmk__alert_node: return "node";
+ case pcmk__alert_fencing: return "fencing";
+ case pcmk__alert_resource: return "resource";
+ case pcmk__alert_attribute: return "attribute";
+ default: return "unknown";
+ }
+}
+#endif
diff --git a/include/crm/common/attrd_internal.h b/include/crm/common/attrd_internal.h
new file mode 100644
index 0000000..9d0b730
--- /dev/null
+++ b/include/crm/common/attrd_internal.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2004-2023 the Pacemaker project contributors
+ *
+ * The version control history for this file may have further details.
+ *
+ * This source code is licensed under the GNU Lesser General Public License
+ * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
+ */
+
+#ifndef PCMK__ATTRD_INTERNAL__H
+# define PCMK__ATTRD_INTERNAL__H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// Options for clients to use with functions below
+enum pcmk__node_attr_opts {
+ pcmk__node_attr_none = 0,
+ pcmk__node_attr_remote = (1 << 0),
+ pcmk__node_attr_private = (1 << 1),
+ pcmk__node_attr_pattern = (1 << 2),
+ pcmk__node_attr_value = (1 << 3),
+ pcmk__node_attr_delay = (1 << 4),
+ pcmk__node_attr_perm = (1 << 5),
+ pcmk__node_attr_sync_local = (1 << 6),
+ pcmk__node_attr_sync_cluster = (1 << 7),
+ pcmk__node_attr_utilization = (1 << 8),
+ pcmk__node_attr_query_all = (1 << 9),
+};
+
+#define pcmk__set_node_attr_flags(node_attr_flags, flags_to_set) do { \
+ node_attr_flags = pcmk__set_flags_as(__func__, __LINE__, \
+ LOG_TRACE, "Node attribute", crm_system_name, \
+ (node_attr_flags), (flags_to_set), #flags_to_set); \
+ } while (0)
+
+#define pcmk__clear_node_attr_flags(node_attr_flags, flags_to_clear) do { \
+ node_attr_flags = pcmk__clear_flags_as(__func__, __LINE__, \
+ LOG_TRACE, "Node attribute", crm_system_name, \
+ (node_attr_flags), (flags_to_clear), #flags_to_clear); \
+ } while (0)
+
+const char *pcmk__node_attr_target(const char *name);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/crm/common/cib.h b/include/crm/common/cib.h
new file mode 100644
index 0000000..e1c4471
--- /dev/null
+++ b/include/crm/common/cib.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2021 the Pacemaker project contributors
+ *
+ * The version control history for this file may have further details.
+ *
+ * This source code is licensed under the GNU Lesser General Public License
+ * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
+ */
+
+#ifndef PCMK__CRM_COMMON_CIB__H
+# define PCMK__CRM_COMMON_CIB__H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <libxml/tree.h> // xmlNode
+
+const char *pcmk_cib_xpath_for(const char *element_name);
+const char *pcmk_cib_parent_name_for(const char *element_name);
+xmlNode *pcmk_find_cib_element(xmlNode *cib, const char *element_name);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // PCMK__COMMON_CIB__H
diff --git a/include/crm/common/cmdline_internal.h b/include/crm/common/cmdline_internal.h
new file mode 100644
index 0000000..db4fd59
--- /dev/null
+++ b/include/crm/common/cmdline_internal.h
@@ -0,0 +1,186 @@
+/*
+ * Copyright 2019-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 PCMK__CMDLINE_INTERNAL__H
+#define PCMK__CMDLINE_INTERNAL__H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <glib.h>
+
+typedef struct {
+ char *summary;
+ char *output_as_descr;
+
+ gboolean version;
+ gboolean quiet;
+ unsigned int verbosity;
+
+ char *output_ty;
+ char *output_dest;
+} pcmk__common_args_t;
+
+/*!
+ * \internal
+ * \brief Allocate a new common args object
+ *
+ * \param[in] summary Summary description of tool for man page
+ *
+ * \return Newly allocated common args object
+ * \note This function will immediately exit the program if memory allocation
+ * fails, since the intent is to call it at the very beginning of a
+ * program, before logging has been set up.
+ */
+pcmk__common_args_t *
+pcmk__new_common_args(const char *summary);
+
+/*!
+ * \internal
+ * \brief Create and return a GOptionContext containing the command line options
+ * supported by all tools.
+ *
+ * \note Formatted output options will be added unless fmts is NULL. This allows
+ * for using this function in tools that have not yet been converted to
+ * formatted output. It should not be NULL in any tool that calls
+ * pcmk__register_formats() as that function adds its own command line
+ * options.
+ *
+ * \param[in,out] common_args A ::pcmk__common_args_t structure where the
+ * results of handling command options will be written.
+ * \param[in] fmts The help string for which formats are supported.
+ * \param[in,out] output_group A ::GOptionGroup that formatted output related
+ * command line arguments should be added to.
+ * \param[in] param_string A string describing any remaining command line
+ * arguments.
+ */
+GOptionContext *
+pcmk__build_arg_context(pcmk__common_args_t *common_args, const char *fmts,
+ GOptionGroup **output_group, const char *param_string);
+
+/*!
+ * \internal
+ * \brief Clean up after pcmk__build_arg_context(). This should be called
+ * instead of ::g_option_context_free at program termination.
+ *
+ * \param[in,out] context Argument context to free
+ */
+void
+pcmk__free_arg_context(GOptionContext *context);
+
+/*!
+ * \internal
+ * \brief Add options to the main application options
+ *
+ * \param[in,out] context Argument context to add options to
+ * \param[in] entries Option entries to add
+ *
+ * \note This is simply a convenience wrapper to reduce duplication
+ */
+void pcmk__add_main_args(GOptionContext *context, const GOptionEntry entries[]);
+
+/*!
+ * \internal
+ * \brief Add an option group to an argument context
+ *
+ * \param[in,out] context Argument context to add group to
+ * \param[in] name Option group name (to be used in --help-NAME)
+ * \param[in] header Header for --help-NAME output
+ * \param[in] desc Short description for --help-NAME option
+ * \param[in] entries Array of options in group
+ *
+ * \note This is simply a convenience wrapper to reduce duplication
+ */
+void pcmk__add_arg_group(GOptionContext *context, const char *name,
+ const char *header, const char *desc,
+ const GOptionEntry entries[]);
+
+/*!
+ * \internal
+ * \brief Prepare the command line for being added to a pcmk__output_t as the
+ * request
+ *
+ * This performs various transformations on the command line arguments, such
+ * as surrounding arguments containing spaces with quotes and escaping any
+ * single quotes in the string.
+ *
+ * \param[in,out] argv Command line (typically from pcmk__cmdline_preproc())
+ */
+gchar *pcmk__quote_cmdline(gchar **argv);
+
+/*!
+ * \internal
+ * \brief Pre-process command line arguments to preserve compatibility with
+ * getopt behavior.
+ *
+ * getopt and glib have slightly different behavior when it comes to processing
+ * single command line arguments. getopt allows this: -x<val>, while glib will
+ * try to handle <val> like it is additional single letter arguments. glib
+ * prefers -x <val> instead.
+ *
+ * This function scans argv, looking for any single letter command line options
+ * (indicated by the 'special' parameter). When one is found, everything after
+ * that argument to the next whitespace is converted into its own value. Single
+ * letter command line options can come in a group after a single dash, but
+ * this function will expand each group into many arguments.
+ *
+ * Long options and anything after "--" is preserved. The result of this function
+ * can then be passed to ::g_option_context_parse_strv for actual processing.
+ *
+ * In pseudocode, this:
+ *
+ * pcmk__cmdline_preproc(4, ["-XbA", "--blah=foo", "-aF", "-Fval", "--", "--extra", "-args"], "aF")
+ *
+ * Would be turned into this:
+ *
+ * ["-X", "-b", "-A", "--blah=foo", "-a", "F", "-F", "val", "--", "--extra", "-args"]
+ *
+ * This function does not modify argv, and the return value is built of copies
+ * of all the command line arguments. It is up to the caller to free this memory
+ * after use.
+ *
+ * \note This function calls g_set_prgname assuming it wasn't previously set and
+ * assuming argv is not NULL. It is not safe to call g_set_prgname more
+ * than once so clients should not do so after calling this function.
+ *
+ * \param[in] argv The command line arguments.
+ * \param[in] special Single-letter command line arguments that take a value.
+ * These letters will all have pre-processing applied.
+ */
+gchar **
+pcmk__cmdline_preproc(char *const *argv, const char *special);
+
+/*!
+ * \internal
+ * \brief Process extra arguments as if they were provided by the user on the
+ * command line.
+ *
+ * \param[in,out] context The command line option processing context.
+ * \param[out] error A place for errors to be collected.
+ * \param[in] format The command line to be processed, potentially with
+ * format specifiers.
+ * \param[in] ... Arguments to be formatted.
+ *
+ * \note The first item in the list of arguments must be the name of the
+ * program, exactly as if the format string were coming from the
+ * command line. Otherwise, the first argument will be ignored.
+ *
+ * \return TRUE if processing succeeded, or FALSE otherwise. If FALSE, error
+ * should be checked and displayed to the user.
+ */
+G_GNUC_PRINTF(3, 4)
+gboolean
+pcmk__force_args(GOptionContext *context, GError **error, const char *format, ...);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/crm/common/health_internal.h b/include/crm/common/health_internal.h
new file mode 100644
index 0000000..277a4c9
--- /dev/null
+++ b/include/crm/common/health_internal.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright 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 PCMK__CRM_COMMON_HEALTH_INTERNAL__H
+#define PCMK__CRM_COMMON_HEALTH_INTERNAL__H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*!
+ * \internal
+ * \brief Possible node health strategies
+ *
+ * \note It would be nice to use this in pe_working_set_t but that will have to
+ * wait for an API backward compatibility break.
+ */
+enum pcmk__health_strategy {
+ pcmk__health_strategy_none,
+ pcmk__health_strategy_no_red,
+ pcmk__health_strategy_only_green,
+ pcmk__health_strategy_progressive,
+ pcmk__health_strategy_custom,
+};
+
+bool pcmk__validate_health_strategy(const char *value);
+
+enum pcmk__health_strategy pcmk__parse_health_strategy(const char *value);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // PCMK__CRM_COMMON_HEALTH_INTERNAL__H
diff --git a/include/crm/common/internal.h b/include/crm/common/internal.h
new file mode 100644
index 0000000..bd98780
--- /dev/null
+++ b/include/crm/common/internal.h
@@ -0,0 +1,358 @@
+/*
+ * Copyright 2015-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 CRM_COMMON_INTERNAL__H
+#define CRM_COMMON_INTERNAL__H
+
+#include <unistd.h> // pid_t, getpid()
+#include <stdbool.h> // bool
+#include <stdint.h> // uint8_t, uint64_t
+
+#include <glib.h> // guint, GList, GHashTable
+#include <libxml/tree.h> // xmlNode
+
+#include <crm/common/util.h> // crm_strdup_printf()
+#include <crm/common/logging.h> // do_crm_log_unlikely(), etc.
+#include <crm/common/mainloop.h> // mainloop_io_t, struct ipc_client_callbacks
+#include <crm/common/health_internal.h>
+#include <crm/common/io_internal.h>
+#include <crm/common/iso8601_internal.h>
+#include <crm/common/results_internal.h>
+#include <crm/common/messages_internal.h>
+#include <crm/common/strings_internal.h>
+#include <crm/common/acl_internal.h>
+
+/* This says whether the current application is a Pacemaker daemon or not,
+ * and is used to change default logging settings such as whether to log to
+ * stderr, etc., as well as a few other details such as whether blackbox signal
+ * handling is enabled.
+ *
+ * It is set when logging is initialized, and does not need to be set directly.
+ */
+extern bool pcmk__is_daemon;
+
+//! Node name of the local node
+extern char *pcmk__our_nodename;
+
+// Number of elements in a statically defined array
+#define PCMK__NELEM(a) ((int) (sizeof(a)/sizeof(a[0])) )
+
+#if SUPPORT_CIBSECRETS
+/* internal CIB utilities (from cib_secrets.c) */
+
+int pcmk__substitute_secrets(const char *rsc_id, GHashTable *params);
+#endif
+
+
+/* internal digest-related utilities (from digest.c) */
+
+bool pcmk__verify_digest(xmlNode *input, const char *expected);
+
+
+/* internal main loop utilities (from mainloop.c) */
+
+int pcmk__add_mainloop_ipc(crm_ipc_t *ipc, int priority, void *userdata,
+ const struct ipc_client_callbacks *callbacks,
+ mainloop_io_t **source);
+guint pcmk__mainloop_timer_get_period(const mainloop_timer_t *timer);
+
+
+/* internal node-related XML utilities (from nodes.c) */
+
+/*!
+ * \internal
+ * \brief Add local node name and ID to an XML node
+ *
+ * \param[in,out] request XML node to modify
+ * \param[in] node The local node's name
+ * \param[in] nodeid The local node's ID (can be 0)
+ */
+void pcmk__xe_add_node(xmlNode *xml, const char *node, int nodeid);
+
+
+/* internal name/value utilities (from nvpair.c) */
+
+int pcmk__scan_nvpair(const char *input, char **name, char **value);
+char *pcmk__format_nvpair(const char *name, const char *value,
+ const char *units);
+
+/*!
+ * \internal
+ * \brief Add a boolean attribute to an XML node.
+ *
+ * \param[in,out] node XML node to add attributes to
+ * \param[in] name XML attribute to create
+ * \param[in] value Value to give to the attribute
+ */
+void
+pcmk__xe_set_bool_attr(xmlNodePtr node, const char *name, bool value);
+
+/*!
+ * \internal
+ * \brief Extract a boolean attribute's value from an XML element
+ *
+ * \param[in] node XML node to get attribute from
+ * \param[in] name XML attribute to get
+ *
+ * \return True if the given \p name is an attribute on \p node and has
+ * the value "true", False in all other cases
+ */
+bool
+pcmk__xe_attr_is_true(const xmlNode *node, const char *name);
+
+/*!
+ * \internal
+ * \brief Extract a boolean attribute's value from an XML element, with
+ * error checking
+ *
+ * \param[in] node XML node to get attribute from
+ * \param[in] name XML attribute to get
+ * \param[out] value Destination for the value of the attribute
+ *
+ * \return EINVAL if \p name or \p value are NULL, ENODATA if \p node is
+ * NULL or the attribute does not exist, pcmk_rc_unknown_format
+ * if the attribute is not a boolean, and pcmk_rc_ok otherwise.
+ *
+ * \note \p value only has any meaning if the return value is pcmk_rc_ok.
+ */
+int
+pcmk__xe_get_bool_attr(const xmlNode *node, const char *name, bool *value);
+
+
+/* internal procfs utilities (from procfs.c) */
+
+pid_t pcmk__procfs_pid_of(const char *name);
+unsigned int pcmk__procfs_num_cores(void);
+int pcmk__procfs_pid2path(pid_t pid, char path[], size_t path_size);
+bool pcmk__procfs_has_pids(void);
+
+/* internal XML schema functions (from xml.c) */
+
+void crm_schema_init(void);
+void crm_schema_cleanup(void);
+
+
+/* internal functions related to process IDs (from pid.c) */
+
+/*!
+ * \internal
+ * \brief Check whether process exists (by PID and optionally executable path)
+ *
+ * \param[in] pid PID of process to check
+ * \param[in] daemon If not NULL, path component to match with procfs entry
+ *
+ * \return Standard Pacemaker return code
+ * \note Particular return codes of interest include pcmk_rc_ok for alive,
+ * ESRCH for process is not alive (verified by kill and/or executable path
+ * match), EACCES for caller unable or not allowed to check. A result of
+ * "alive" is less reliable when \p daemon is not provided or procfs is
+ * not available, since there is no guarantee that the PID has not been
+ * recycled for another process.
+ * \note This function cannot be used to verify \e authenticity of the process.
+ */
+int pcmk__pid_active(pid_t pid, const char *daemon);
+
+int pcmk__read_pidfile(const char *filename, pid_t *pid);
+int pcmk__pidfile_matches(const char *filename, pid_t expected_pid,
+ const char *expected_name, pid_t *pid);
+int pcmk__lock_pidfile(const char *filename, const char *name);
+
+
+/* internal functions related to resource operations (from operations.c) */
+
+// printf-style format to create operation ID from resource, action, interval
+#define PCMK__OP_FMT "%s_%s_%u"
+
+char *pcmk__op_key(const char *rsc_id, const char *op_type, guint interval_ms);
+char *pcmk__notify_key(const char *rsc_id, const char *notify_type,
+ const char *op_type);
+char *pcmk__transition_key(int transition_id, int action_id, int target_rc,
+ const char *node);
+void pcmk__filter_op_for_digest(xmlNode *param_set);
+bool pcmk__is_fencing_action(const char *action);
+
+
+// bitwise arithmetic utilities
+
+/*!
+ * \internal
+ * \brief Set specified flags in a flag group
+ *
+ * \param[in] function Function name of caller
+ * \param[in] line Line number of caller
+ * \param[in] log_level Log a message at this level
+ * \param[in] flag_type Label describing this flag group (for logging)
+ * \param[in] target Name of object whose flags these are (for logging)
+ * \param[in] flag_group Flag group being manipulated
+ * \param[in] flags Which flags in the group should be set
+ * \param[in] flags_str Readable equivalent of \p flags (for logging)
+ *
+ * \return Possibly modified flag group
+ */
+static inline uint64_t
+pcmk__set_flags_as(const char *function, int line, uint8_t log_level,
+ const char *flag_type, const char *target,
+ uint64_t flag_group, uint64_t flags, const char *flags_str)
+{
+ uint64_t result = flag_group | flags;
+
+ if (result != flag_group) {
+ do_crm_log_unlikely(log_level,
+ "%s flags %#.8llx (%s) for %s set by %s:%d",
+ ((flag_type == NULL)? "Group of" : flag_type),
+ (unsigned long long) flags,
+ ((flags_str == NULL)? "flags" : flags_str),
+ ((target == NULL)? "target" : target),
+ function, line);
+ }
+ return result;
+}
+
+/*!
+ * \internal
+ * \brief Clear specified flags in a flag group
+ *
+ * \param[in] function Function name of caller
+ * \param[in] line Line number of caller
+ * \param[in] log_level Log a message at this level
+ * \param[in] flag_type Label describing this flag group (for logging)
+ * \param[in] target Name of object whose flags these are (for logging)
+ * \param[in] flag_group Flag group being manipulated
+ * \param[in] flags Which flags in the group should be cleared
+ * \param[in] flags_str Readable equivalent of \p flags (for logging)
+ *
+ * \return Possibly modified flag group
+ */
+static inline uint64_t
+pcmk__clear_flags_as(const char *function, int line, uint8_t log_level,
+ const char *flag_type, const char *target,
+ uint64_t flag_group, uint64_t flags, const char *flags_str)
+{
+ uint64_t result = flag_group & ~flags;
+
+ if (result != flag_group) {
+ do_crm_log_unlikely(log_level,
+ "%s flags %#.8llx (%s) for %s cleared by %s:%d",
+ ((flag_type == NULL)? "Group of" : flag_type),
+ (unsigned long long) flags,
+ ((flags_str == NULL)? "flags" : flags_str),
+ ((target == NULL)? "target" : target),
+ function, line);
+ }
+ return result;
+}
+
+// miscellaneous utilities (from utils.c)
+
+void pcmk__daemonize(const char *name, const char *pidfile);
+void pcmk__panic(const char *origin);
+pid_t pcmk__locate_sbd(void);
+void pcmk__sleep_ms(unsigned int ms);
+
+extern int pcmk__score_red;
+extern int pcmk__score_green;
+extern int pcmk__score_yellow;
+
+/*!
+ * \internal
+ * \brief Resize a dynamically allocated memory block
+ *
+ * \param[in] ptr Memory block to resize (or NULL to allocate new memory)
+ * \param[in] size New size of memory block in bytes (must be > 0)
+ *
+ * \return Pointer to resized memory block
+ *
+ * \note This asserts on error, so the result is guaranteed to be non-NULL
+ * (which is the main advantage of this over directly using realloc()).
+ */
+static inline void *
+pcmk__realloc(void *ptr, size_t size)
+{
+ void *new_ptr;
+
+ // realloc(p, 0) can replace free(p) but this wrapper can't
+ CRM_ASSERT(size > 0);
+
+ new_ptr = realloc(ptr, size);
+ if (new_ptr == NULL) {
+ free(ptr);
+ abort();
+ }
+ return new_ptr;
+}
+
+
+static inline char *
+pcmk__getpid_s(void)
+{
+ return crm_strdup_printf("%lu", (unsigned long) getpid());
+}
+
+// More efficient than g_list_length(list) == 1
+static inline bool
+pcmk__list_of_1(GList *list)
+{
+ return list && (list->next == NULL);
+}
+
+// More efficient than g_list_length(list) > 1
+static inline bool
+pcmk__list_of_multiple(GList *list)
+{
+ return list && (list->next != NULL);
+}
+
+/* convenience functions for failure-related node attributes */
+
+#define PCMK__FAIL_COUNT_PREFIX "fail-count"
+#define PCMK__LAST_FAILURE_PREFIX "last-failure"
+
+/*!
+ * \internal
+ * \brief Generate a failure-related node attribute name for a resource
+ *
+ * \param[in] prefix Start of attribute name
+ * \param[in] rsc_id Resource name
+ * \param[in] op Operation name
+ * \param[in] interval_ms Operation interval
+ *
+ * \return Newly allocated string with attribute name
+ *
+ * \note Failure attributes are named like PREFIX-RSC#OP_INTERVAL (for example,
+ * "fail-count-myrsc#monitor_30000"). The '#' is used because it is not
+ * a valid character in a resource ID, to reliably distinguish where the
+ * operation name begins. The '_' is used simply to be more comparable to
+ * action labels like "myrsc_monitor_30000".
+ */
+static inline char *
+pcmk__fail_attr_name(const char *prefix, const char *rsc_id, const char *op,
+ guint interval_ms)
+{
+ CRM_CHECK(prefix && rsc_id && op, return NULL);
+ return crm_strdup_printf("%s-%s#%s_%u", prefix, rsc_id, op, interval_ms);
+}
+
+static inline char *
+pcmk__failcount_name(const char *rsc_id, const char *op, guint interval_ms)
+{
+ return pcmk__fail_attr_name(PCMK__FAIL_COUNT_PREFIX, rsc_id, op,
+ interval_ms);
+}
+
+static inline char *
+pcmk__lastfailure_name(const char *rsc_id, const char *op, guint interval_ms)
+{
+ return pcmk__fail_attr_name(PCMK__LAST_FAILURE_PREFIX, rsc_id, op,
+ interval_ms);
+}
+
+// internal resource agent functions (from agents.c)
+int pcmk__effective_rc(int rc);
+
+#endif /* CRM_COMMON_INTERNAL__H */
diff --git a/include/crm/common/io_internal.h b/include/crm/common/io_internal.h
new file mode 100644
index 0000000..a8e1f28
--- /dev/null
+++ b/include/crm/common/io_internal.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright 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 PCMK__CRM_COMMON_IO_INTERNAL__H
+# define PCMK__CRM_COMMON_IO_INTERNAL__H
+
+#include <fcntl.h> // open()
+#include <stdbool.h> // bool
+#include <unistd.h> // uid_t, gid_t
+
+int pcmk__real_path(const char *path, char **resolved_path);
+
+char *pcmk__series_filename(const char *directory, const char *series,
+ int sequence, bool bzip);
+int pcmk__read_series_sequence(const char *directory, const char *series,
+ unsigned int *seq);
+void pcmk__write_series_sequence(const char *directory, const char *series,
+ unsigned int sequence, int max);
+int pcmk__chown_series_sequence(const char *directory, const char *series,
+ uid_t uid, gid_t gid);
+
+int pcmk__build_path(const char *path_c, mode_t mode);
+char *pcmk__full_path(const char *filename, const char *dirname);
+bool pcmk__daemon_can_write(const char *dir, const char *file);
+void pcmk__sync_directory(const char *name);
+
+int pcmk__file_contents(const char *filename, char **contents);
+int pcmk__write_sync(int fd, const char *contents);
+int pcmk__set_nonblocking(int fd);
+const char *pcmk__get_tmpdir(void);
+
+void pcmk__close_fds_in_child(bool);
+
+/*!
+ * \internal
+ * \brief Open /dev/null to consume next available file descriptor
+ *
+ * Open /dev/null, disregarding the result. This is intended when daemonizing to
+ * be able to null stdin, stdout, and stderr.
+ *
+ * \param[in] flags O_RDONLY (stdin) or O_WRONLY (stdout and stderr)
+ */
+static inline void
+pcmk__open_devnull(int flags)
+{
+ // Static analysis clutter
+ // cppcheck-suppress leakReturnValNotUsed
+ (void) open("/dev/null", flags);
+}
+
+#endif // PCMK__CRM_COMMON_IO_INTERNAL__H
diff --git a/include/crm/common/ipc.h b/include/crm/common/ipc.h
new file mode 100644
index 0000000..3d4ee10
--- /dev/null
+++ b/include/crm/common/ipc.h
@@ -0,0 +1,233 @@
+/*
+ * 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 PCMK__CRM_COMMON_IPC__H
+# define PCMK__CRM_COMMON_IPC__H
+
+
+#include <sys/uio.h>
+#include <qb/qbipcc.h>
+#include <crm/common/xml.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * \file
+ * \brief IPC interface to Pacemaker daemons
+ *
+ * \ingroup core
+ */
+
+/*
+ * Message creation utilities
+ *
+ * These are used for both IPC messages and cluster layer messages. However,
+ * since this is public API, they stay in this header for backward
+ * compatibility.
+ */
+
+#define create_reply(request, xml_response_data) \
+ create_reply_adv(request, xml_response_data, __func__)
+
+xmlNode *create_reply_adv(const xmlNode *request, xmlNode *xml_response_data,
+ const char *origin);
+
+#define create_request(task, xml_data, host_to, sys_to, sys_from, uuid_from) \
+ create_request_adv(task, xml_data, host_to, sys_to, sys_from, uuid_from, \
+ __func__)
+
+xmlNode *create_request_adv(const char *task, xmlNode *xml_data,
+ const char *host_to, const char *sys_to,
+ const char *sys_from, const char *uuid_from,
+ const char *origin);
+
+
+/*
+ * The library supports two methods of creating IPC connections. The older code
+ * allows connecting to any arbitrary IPC name. The newer code only allows
+ * connecting to one of the Pacemaker daemons.
+ *
+ * As daemons are converted to use the new model, the old functions should be
+ * considered deprecated for use with those daemons. Once all daemons are
+ * converted, the old functions should be officially deprecated as public API
+ * and eventually made internal API.
+ */
+
+/*
+ * Pacemaker daemon IPC
+ */
+
+//! Available IPC interfaces
+enum pcmk_ipc_server {
+ pcmk_ipc_attrd, //!< Attribute manager
+ pcmk_ipc_based, //!< CIB manager
+ pcmk_ipc_controld, //!< Controller
+ pcmk_ipc_execd, //!< Executor
+ pcmk_ipc_fenced, //!< Fencer
+ pcmk_ipc_pacemakerd, //!< Launcher
+ pcmk_ipc_schedulerd, //!< Scheduler
+};
+
+//! Possible event types that an IPC event callback can be called for
+enum pcmk_ipc_event {
+ pcmk_ipc_event_connect, //!< Result of asynchronous connection attempt
+ pcmk_ipc_event_disconnect, //!< Termination of IPC connection
+ pcmk_ipc_event_reply, //!< Daemon's reply to client IPC request
+ pcmk_ipc_event_notify, //!< Notification from daemon
+};
+
+//! How IPC replies should be dispatched
+enum pcmk_ipc_dispatch {
+ pcmk_ipc_dispatch_main, //!< Attach IPC to GMainLoop for dispatch
+ pcmk_ipc_dispatch_poll, //!< Caller will poll and dispatch IPC
+ pcmk_ipc_dispatch_sync, //!< Sending a command will wait for any reply
+};
+
+//! Client connection to Pacemaker IPC
+typedef struct pcmk_ipc_api_s pcmk_ipc_api_t;
+
+/*!
+ * \brief Callback function type for Pacemaker daemon IPC APIs
+ *
+ * \param[in,out] api IPC API connection
+ * \param[in] event_type The type of event that occurred
+ * \param[in] status Event status
+ * \param[in,out] event_data Event-specific data
+ * \param[in,out] user_data Caller data provided when callback was registered
+ *
+ * \note For connection and disconnection events, event_data may be NULL (for
+ * local IPC) or the name of the connected node (for remote IPC, for
+ * daemons that support that). For reply and notify events, event_data is
+ * defined by the specific daemon API.
+ */
+typedef void (*pcmk_ipc_callback_t)(pcmk_ipc_api_t *api,
+ enum pcmk_ipc_event event_type,
+ crm_exit_t status,
+ void *event_data, void *user_data);
+
+int pcmk_new_ipc_api(pcmk_ipc_api_t **api, enum pcmk_ipc_server server);
+
+void pcmk_free_ipc_api(pcmk_ipc_api_t *api);
+
+int pcmk_connect_ipc(pcmk_ipc_api_t *api, enum pcmk_ipc_dispatch dispatch_type);
+
+void pcmk_disconnect_ipc(pcmk_ipc_api_t *api);
+
+int pcmk_poll_ipc(const pcmk_ipc_api_t *api, int timeout_ms);
+
+void pcmk_dispatch_ipc(pcmk_ipc_api_t *api);
+
+void pcmk_register_ipc_callback(pcmk_ipc_api_t *api, pcmk_ipc_callback_t cb,
+ void *user_data);
+
+const char *pcmk_ipc_name(const pcmk_ipc_api_t *api, bool for_log);
+
+bool pcmk_ipc_is_connected(pcmk_ipc_api_t *api);
+
+int pcmk_ipc_purge_node(pcmk_ipc_api_t *api, const char *node_name,
+ uint32_t nodeid);
+
+
+/*
+ * Generic IPC API (to eventually be deprecated as public API and made internal)
+ */
+
+/* *INDENT-OFF* */
+enum crm_ipc_flags
+{
+ crm_ipc_flags_none = 0x00000000,
+
+ crm_ipc_compressed = 0x00000001, /* Message has been compressed */
+
+ crm_ipc_proxied = 0x00000100, /* _ALL_ replies to proxied connections need to be sent as events */
+ crm_ipc_client_response = 0x00000200, /* A Response is expected in reply */
+
+ // These are options for Pacemaker's internal use only (pcmk__ipc_send_*())
+ crm_ipc_server_event = 0x00010000, /* Send an Event instead of a Response */
+ crm_ipc_server_free = 0x00020000, /* Free the iovec after sending */
+ crm_ipc_proxied_relay_response = 0x00040000, /* all replies to proxied connections are sent as events, this flag preserves whether the event should be treated as an actual event, or a response.*/
+
+#if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1)
+ crm_ipc_server_info = 0x00100000, //!< \deprecated Unused
+ crm_ipc_server_error = 0x00200000, //!< \deprecated Unused
+#endif
+};
+/* *INDENT-ON* */
+
+typedef struct crm_ipc_s crm_ipc_t;
+
+crm_ipc_t *crm_ipc_new(const char *name, size_t max_size);
+bool crm_ipc_connect(crm_ipc_t * client);
+void crm_ipc_close(crm_ipc_t * client);
+void crm_ipc_destroy(crm_ipc_t * client);
+void pcmk_free_ipc_event(struct iovec *event);
+
+int crm_ipc_send(crm_ipc_t * client, xmlNode * message, enum crm_ipc_flags flags,
+ int32_t ms_timeout, xmlNode ** reply);
+
+int crm_ipc_get_fd(crm_ipc_t * client);
+bool crm_ipc_connected(crm_ipc_t * client);
+int crm_ipc_ready(crm_ipc_t * client);
+long crm_ipc_read(crm_ipc_t * client);
+const char *crm_ipc_buffer(crm_ipc_t * client);
+uint32_t crm_ipc_buffer_flags(crm_ipc_t * client);
+const char *crm_ipc_name(crm_ipc_t * client);
+unsigned int crm_ipc_default_buffer_size(void);
+
+/*!
+ * \brief Check the authenticity of the IPC socket peer process (legacy)
+ *
+ * If everything goes well, peer's authenticity is verified by the means
+ * of comparing against provided referential UID and GID (either satisfies),
+ * and the result of this check can be deduced from the return value.
+ * As an exception, detected UID of 0 ("root") satisfies arbitrary
+ * provided referential daemon's credentials.
+ *
+ * \param[in] sock IPC related, connected Unix socket to check peer of
+ * \param[in] refuid referential UID to check against
+ * \param[in] refgid referential GID to check against
+ * \param[out] gotpid to optionally store obtained PID of the peer
+ * (not available on FreeBSD, special value of 1
+ * used instead, and the caller is required to
+ * special case this value respectively)
+ * \param[out] gotuid to optionally store obtained UID of the peer
+ * \param[out] gotgid to optionally store obtained GID of the peer
+ *
+ * \return 0 if IPC related socket's peer is not authentic given the
+ * referential credentials (see above), 1 if it is,
+ * negative value on error (generally expressing -errno unless
+ * it was zero even on nonhappy path, -pcmk_err_generic is
+ * returned then; no message is directly emitted)
+ *
+ * \note While this function is tolerant on what constitutes authorized
+ * IPC daemon process (its effective user matches UID=0 or \p refuid,
+ * or at least its group matches \p refgid), either or both (in case
+ * of UID=0) mismatches on the expected credentials of such peer
+ * process \e shall be investigated at the caller when value of 1
+ * gets returned there, since higher-than-expected privileges in
+ * respect to the expected/intended credentials possibly violate
+ * the least privilege principle and may pose an additional risk
+ * (i.e. such accidental inconsistency shall be eventually fixed).
+ */
+int crm_ipc_is_authentic_process(int sock, uid_t refuid, gid_t refgid,
+ pid_t *gotpid, uid_t *gotuid, gid_t *gotgid);
+
+/* This is controller-specific but is declared in this header for C API
+ * backward compatibility.
+ */
+xmlNode *create_hello_message(const char *uuid, const char *client_name,
+ const char *major_version, const char *minor_version);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/crm/common/ipc_attrd_internal.h b/include/crm/common/ipc_attrd_internal.h
new file mode 100644
index 0000000..b1b7584
--- /dev/null
+++ b/include/crm/common/ipc_attrd_internal.h
@@ -0,0 +1,198 @@
+/*
+ * Copyright 2022-2023 the Pacemaker project contributors
+ *
+ * The version control history for this file may have further details.
+ *
+ * This source code is licensed under the GNU Lesser General Public License
+ * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
+ */
+
+#ifndef PCMK__CRM_COMMON_IPC_ATTRD_INTERNAL__H
+# define PCMK__CRM_COMMON_IPC_ATTRD_INTERNAL__H
+
+#include <glib.h> // GList
+#include <crm/common/ipc.h> // pcmk_ipc_api_t
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+//! Possible types of attribute manager replies
+enum pcmk__attrd_api_reply {
+ pcmk__attrd_reply_unknown,
+ pcmk__attrd_reply_query,
+};
+
+// Information passed with pcmk__attrd_reply_query
+typedef struct {
+ const char *node;
+ const char *name;
+ const char *value;
+} pcmk__attrd_query_pair_t;
+
+/*!
+ * Attribute manager reply passed to event callback
+ *
+ * \note The pointers in the reply are only guaranteed to be meaningful for the
+ * execution of the callback; if the values are needed for later, the
+ * callback should copy them.
+ */
+typedef struct {
+ enum pcmk__attrd_api_reply reply_type;
+
+ union {
+ // pcmk__attrd_reply_query
+ GList *pairs;
+ } data;
+} pcmk__attrd_api_reply_t;
+
+/*!
+ * \internal
+ * \brief Send a request to pacemaker-attrd to clear resource failure
+ *
+ * \param[in,out] api pacemaker-attrd IPC object
+ * \param[in] node Affect only this node (or NULL for all nodes)
+ * \param[in] resource Name of resource to clear (or NULL for all)
+ * \param[in] operation Name of operation to clear (or NULL for all)
+ * \param[in] interval_spec If operation is not NULL, its interval
+ * \param[in] user_name ACL user to pass to pacemaker-attrd
+ * \param[in] options Bitmask of pcmk__node_attr_opts
+ *
+ * \note If \p api is NULL, a new temporary connection will be created
+ * just for this operation and destroyed afterwards. If \p api is
+ * not NULL but is not yet connected to pacemaker-attrd, the object
+ * will be connected for this operation and left connected afterwards.
+ * This allows for reusing an IPC connection.
+ *
+ * \return Standard Pacemaker return code
+ */
+int pcmk__attrd_api_clear_failures(pcmk_ipc_api_t *api, const char *node,
+ const char *resource, const char *operation,
+ const char *interval_spec, const char *user_name,
+ uint32_t options);
+
+/*!
+ * \internal
+ *
+ * \brief Delete a previously set attribute by setting its value to NULL
+ *
+ * \param[in,out] api Connection to pacemaker-attrd (or NULL to use
+ * a temporary new connection)
+ * \param[in] node Delete attribute for this node (or NULL for local)
+ * \param[in] name Attribute name
+ * \param[in] options Bitmask of pcmk__node_attr_opts
+ *
+ * \return Standard Pacemaker return code
+ */
+int pcmk__attrd_api_delete(pcmk_ipc_api_t *api, const char *node, const char *name,
+ uint32_t options);
+
+/*!
+ * \internal
+ * \brief Purge a node from pacemaker-attrd
+ *
+ * \param[in,out] api pacemaker-attrd IPC object
+ * \param[in] node Node to remove
+ *
+ * \note If \p api is NULL, a new temporary connection will be created
+ * just for this operation and destroyed afterwards. If \p api is
+ * not NULL but is not yet connected to pacemaker-attrd, the object
+ * will be connected for this operation and left connected afterwards.
+ * This allows for reusing an IPC connection.
+ *
+ * \return Standard Pacemaker return code
+ */
+int pcmk__attrd_api_purge(pcmk_ipc_api_t *api, const char *node);
+
+/*!
+ * \internal
+ * \brief Get the value of an attribute from pacemaker-attrd
+ *
+ * \param[in,out] api Connection to pacemaker-attrd
+ * \param[in] node Look up the attribute for this node
+ * (or NULL for the local node)
+ * \param[in] name Attribute name
+ * \param[in] options Bitmask of pcmk__node_attr_opts
+ *
+ * \note Passing pcmk__node_attr_query_all will cause the function to query
+ * the value of \p name on all nodes, regardless of the value of \p node.
+ *
+ * \return Standard Pacemaker return code
+ */
+int pcmk__attrd_api_query(pcmk_ipc_api_t *api, const char *node, const char *name,
+ uint32_t options);
+
+/*!
+ * \internal
+ * \brief Tell pacemaker-attrd to update the CIB with current values
+ *
+ * \param[in,out] api pacemaker-attrd IPC object
+ * \param[in] node Affect only this node (or NULL for all nodes)
+ *
+ * \note If \p api is NULL, a new temporary connection will be created
+ * just for this operation and destroyed afterwards. If \p api is
+ * not NULL but is not yet connected to pacemaker-attrd, the object
+ * will be connected for this operation and left connected afterwards.
+ * This allows for reusing an IPC connection.
+ *
+ * \return Standard Pacemaker return code
+ */
+int pcmk__attrd_api_refresh(pcmk_ipc_api_t *api, const char *node);
+
+/*!
+ * \internal
+ * \brief Update an attribute's value, time to wait, or both
+ *
+ * \param[in,out] api pacemaker-attrd IPC object
+ * \param[in] node Affect only this node (or NULL for current node)
+ * \param[in] name Attribute name
+ * \param[in] value The attribute's new value, or NULL to unset
+ * \param[in] dampen The new time to wait value, or NULL to unset
+ * \param[in] set ID of attribute set to use (or NULL for first)
+ * \param[in] user_name ACL user to pass to pacemaker-attrd
+ * \param[in] options Bitmask of pcmk__node_attr_opts
+ *
+ * \note If \p api is NULL, a new temporary connection will be created
+ * just for this operation and destroyed afterwards. If \p api is
+ * not NULL but is not yet connected to pacemaker-attrd, the object
+ * will be connected for this operation and left connected afterwards.
+ * This allows for reusing an IPC connection.
+ *
+ * \return Standard Pacemaker return code
+ */
+int pcmk__attrd_api_update(pcmk_ipc_api_t *api, const char *node, const char *name,
+ const char *value, const char *dampen, const char *set,
+ const char *user_name, uint32_t options);
+
+/*!
+ * \internal
+ * \brief Like pcmk__attrd_api_update, but for multiple attributes at once
+ *
+ * \param[in,out] api pacemaker-attrd IPC object
+ * \param[in,out] attrs A list of pcmk__attr_query_pair_t structs
+ * \param[in] dampen The new time to wait value, or NULL to unset
+ * \param[in] set ID of attribute set to use (or NULL for first)
+ * \param[in] user_name ACL user to pass to pacemaker-attrd
+ * \param[in] options Bitmask of pcmk__node_attr_opts
+ *
+ * \note If \p api is NULL, a new temporary connection will be created
+ * just for this operation and destroyed afterwards. If \p api is
+ * not NULL but is not yet connected to pacemaker-attrd, the object
+ * will be connected for this operation and left connected afterwards.
+ * This allows for reusing an IPC connection.
+ *
+ * \note Not all attrd versions support setting multiple attributes at once.
+ * For those servers that do not, this function will fall back to just
+ * sending a separate IPC request for each attribute.
+ *
+ * \return Standard Pacemaker return code
+ */
+int pcmk__attrd_api_update_list(pcmk_ipc_api_t *api, GList *attrs,
+ const char *dampen, const char *set,
+ const char *user_name, uint32_t options);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // PCMK__CRM_COMMON_IPC_ATTRD_INTERNAL__H
diff --git a/include/crm/common/ipc_controld.h b/include/crm/common/ipc_controld.h
new file mode 100644
index 0000000..6deba48
--- /dev/null
+++ b/include/crm/common/ipc_controld.h
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2020-2021 the Pacemaker project contributors
+ *
+ * The version control history for this file may have further details.
+ *
+ * This source code is licensed under the GNU Lesser General Public License
+ * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
+ */
+
+#ifndef PCMK__CRM_COMMON_IPC_CONTROLD__H
+# define PCMK__CRM_COMMON_IPC_CONTROLD__H
+
+
+#include <stdbool.h> // bool
+#include <glib.h> // GList
+#include <libxml/tree.h> // xmlNode
+#include <crm/common/ipc.h> // pcmk_ipc_api_t
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * \file
+ * \brief IPC commands for Pacemaker controller
+ *
+ * \ingroup core
+ */
+
+//! Possible types of controller replies
+enum pcmk_controld_api_reply {
+ pcmk_controld_reply_unknown,
+ pcmk_controld_reply_reprobe,
+ pcmk_controld_reply_info,
+ pcmk_controld_reply_resource,
+ pcmk_controld_reply_ping,
+ pcmk_controld_reply_nodes,
+};
+
+// Node information passed with pcmk_controld_reply_nodes
+typedef struct {
+ uint32_t id;
+ const char *uname;
+ const char *state;
+} pcmk_controld_api_node_t;
+
+/*!
+ * Controller reply passed to event callback
+ *
+ * \note Shutdown and election calls have no reply. Reprobe calls are
+ * acknowledged but contain no data (reply_type will be the only item
+ * set). Node info and ping calls have their own reply data. Fail and
+ * refresh calls use the resource reply type and reply data.
+ * \note The pointers in the reply are only guaranteed to be meaningful for the
+ * execution of the callback; if the values are needed for later, the
+ * callback should copy them.
+ */
+typedef struct {
+ enum pcmk_controld_api_reply reply_type;
+ const char *feature_set; //!< CRM feature set advertised by controller
+ const char *host_from; //!< Name of node that sent reply
+
+ union {
+ // pcmk_controld_reply_info
+ struct {
+ bool have_quorum;
+ bool is_remote;
+ int id;
+ const char *uuid;
+ const char *uname;
+ const char *state;
+ } node_info;
+
+ // pcmk_controld_reply_resource
+ struct {
+ xmlNode *node_state; //<! Resource operation history XML
+ } resource;
+
+ // pcmk_controld_reply_ping
+ struct {
+ const char *sys_from;
+ const char *fsa_state;
+ const char *result;
+ } ping;
+
+ // pcmk_controld_reply_nodes
+ GList *nodes; // list of pcmk_controld_api_node_t *
+ } data;
+} pcmk_controld_api_reply_t;
+
+int pcmk_controld_api_reprobe(pcmk_ipc_api_t *api, const char *target_node,
+ const char *router_node);
+int pcmk_controld_api_node_info(pcmk_ipc_api_t *api, uint32_t nodeid);
+int pcmk_controld_api_fail(pcmk_ipc_api_t *api, const char *target_node,
+ const char *router_node, const char *rsc_id,
+ const char *rsc_long_id, const char *standard,
+ const char *provider, const char *type);
+int pcmk_controld_api_refresh(pcmk_ipc_api_t *api, const char *target_node,
+ const char *router_node, const char *rsc_id,
+ const char *rsc_long_id, const char *standard,
+ const char *provider, const char *type,
+ bool cib_only);
+int pcmk_controld_api_ping(pcmk_ipc_api_t *api, const char *node_name);
+int pcmk_controld_api_list_nodes(pcmk_ipc_api_t *api);
+unsigned int pcmk_controld_api_replies_expected(const pcmk_ipc_api_t *api);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // PCMK__CRM_COMMON_IPC_CONTROLD__H
diff --git a/include/crm/common/ipc_internal.h b/include/crm/common/ipc_internal.h
new file mode 100644
index 0000000..5099dda
--- /dev/null
+++ b/include/crm/common/ipc_internal.h
@@ -0,0 +1,293 @@
+/*
+ * Copyright 2013-2023 the Pacemaker project contributors
+ *
+ * The version control history for this file may have further details.
+ *
+ * This source code is licensed under the GNU Lesser General Public License
+ * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
+ */
+
+#ifndef PCMK__IPC_INTERNAL_H
+#define PCMK__IPC_INTERNAL_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdbool.h> // bool
+#include <stdint.h> // uint32_t, uint64_t, UINT64_C()
+#include <sys/uio.h> // struct iovec
+#include <sys/types.h> // uid_t, gid_t, pid_t, size_t
+
+#ifdef HAVE_GNUTLS_GNUTLS_H
+# include <gnutls/gnutls.h> // gnutls_session_t
+#endif
+
+#include <glib.h> // guint, gpointer, GQueue, ...
+#include <libxml/tree.h> // xmlNode
+#include <qb/qbipcs.h> // qb_ipcs_connection_t, ...
+
+#include <crm_config.h> // HAVE_GETPEEREID
+#include <crm/common/ipc.h>
+#include <crm/common/ipc_controld.h> // pcmk_controld_api_reply
+#include <crm/common/ipc_pacemakerd.h> // pcmk_pacemakerd_{api_reply,state}
+#include <crm/common/mainloop.h> // mainloop_io_t
+
+/*
+ * XML attribute names used only by internal code
+ */
+
+#define PCMK__XA_IPC_PROTO_VERSION "ipc-protocol-version"
+
+/* denotes "non yieldable PID" on FreeBSD, or actual PID1 in scenarios that
+ require a delicate handling anyway (socket-based activation with systemd);
+ we can be reasonably sure that this PID is never possessed by the actual
+ child daemon, as it gets taken either by the proper init, or by pacemakerd
+ itself (i.e. this precludes anything else); note that value of zero
+ is meant to carry "unset" meaning, and better not to bet on/conditionalize
+ over signedness of pid_t */
+#define PCMK__SPECIAL_PID 1
+
+// Timeout (in seconds) to use for IPC client sends, reply waits, etc.
+#define PCMK__IPC_TIMEOUT 120
+
+#if defined(HAVE_GETPEEREID)
+/* on FreeBSD, we don't want to expose "non-yieldable PID" (leading to
+ "IPC liveness check only") as its nominal representation, which could
+ cause confusion -- this is unambiguous as long as there's no
+ socket-based activation like with systemd (very improbable) */
+#define PCMK__SPECIAL_PID_AS_0(p) (((p) == PCMK__SPECIAL_PID) ? 0 : (p))
+#else
+#define PCMK__SPECIAL_PID_AS_0(p) (p)
+#endif
+
+/*!
+ * \internal
+ * \brief Check the authenticity and liveness of the process via IPC end-point
+ *
+ * When IPC daemon under given IPC end-point (name) detected, its authenticity
+ * is verified by the means of comparing against provided referential UID and
+ * GID, and the result of this check can be deduced from the return value.
+ * As an exception, referential UID of 0 (~ root) satisfies arbitrary
+ * detected daemon's credentials.
+ *
+ * \param[in] name IPC name to base the search on
+ * \param[in] refuid referential UID to check against
+ * \param[in] refgid referential GID to check against
+ * \param[out] gotpid to optionally store obtained PID of the found process
+ * upon returning 1 or -2
+ * (not available on FreeBSD, special value of 1,
+ * see PCMK__SPECIAL_PID, used instead, and the caller
+ * is required to special case this value respectively)
+ *
+ * \return Standard Pacemaker return code
+ *
+ * \note Return codes of particular interest include pcmk_rc_ipc_unresponsive
+ * indicating that no trace of IPC liveness was detected, and
+ * pcmk_rc_ipc_unauthorized indicating that the IPC endpoint is blocked by
+ * an unauthorized process.
+ * \note This function emits a log message for return codes other than
+ * pcmk_rc_ok and pcmk_rc_ipc_unresponsive, and when there isn't a perfect
+ * match in respect to \p reguid and/or \p refgid, for a possible
+ * least privilege principle violation.
+ *
+ * \see crm_ipc_is_authentic_process
+ */
+int pcmk__ipc_is_authentic_process_active(const char *name, uid_t refuid,
+ gid_t refgid, pid_t *gotpid);
+
+
+/*
+ * Server-related
+ */
+
+typedef struct pcmk__client_s pcmk__client_t;
+
+struct pcmk__remote_s {
+ /* Shared */
+ char *buffer;
+ size_t buffer_size;
+ size_t buffer_offset;
+ int auth_timeout;
+ int tcp_socket;
+ mainloop_io_t *source;
+ time_t uptime;
+
+ /* CIB-only */
+ char *token;
+
+ /* TLS only */
+# ifdef HAVE_GNUTLS_GNUTLS_H
+ gnutls_session_t *tls_session;
+# endif
+};
+
+enum pcmk__client_flags {
+ // Lower 32 bits are reserved for server (not library) use
+
+ // Next 8 bits are reserved for client type (sort of a cheap enum)
+
+ //! Client uses plain IPC
+ pcmk__client_ipc = (UINT64_C(1) << 32),
+
+ //! Client uses TCP connection
+ pcmk__client_tcp = (UINT64_C(1) << 33),
+
+# ifdef HAVE_GNUTLS_GNUTLS_H
+ //! Client uses TCP with TLS
+ pcmk__client_tls = (UINT64_C(1) << 34),
+# endif
+
+ // The rest are client attributes
+
+ //! Client IPC is proxied
+ pcmk__client_proxied = (UINT64_C(1) << 40),
+
+ //! Client is run by root or cluster user
+ pcmk__client_privileged = (UINT64_C(1) << 41),
+
+ //! Local client to be proxied
+ pcmk__client_to_proxy = (UINT64_C(1) << 42),
+
+ /*!
+ * \brief Client IPC connection accepted
+ *
+ * Used only for remote CIB connections via \c remote-tls-port.
+ */
+ pcmk__client_authenticated = (UINT64_C(1) << 43),
+
+# ifdef HAVE_GNUTLS_GNUTLS_H
+ //! Client TLS handshake is complete
+ pcmk__client_tls_handshake_complete = (UINT64_C(1) << 44),
+# endif
+};
+
+#define PCMK__CLIENT_TYPE(client) ((client)->flags & UINT64_C(0xff00000000))
+
+struct pcmk__client_s {
+ unsigned int pid;
+
+ char *id;
+ char *name;
+ char *user;
+ uint64_t flags; // Group of pcmk__client_flags
+
+ int request_id;
+ void *userdata;
+
+ int event_timer;
+ GQueue *event_queue;
+
+ /* Depending on the client type, only some of the following will be
+ * populated/valid. @TODO Maybe convert to a union.
+ */
+
+ qb_ipcs_connection_t *ipcs; /* IPC */
+
+ struct pcmk__remote_s *remote; /* TCP/TLS */
+
+ unsigned int queue_backlog; /* IPC queue length after last flush */
+ unsigned int queue_max; /* Evict client whose queue grows this big */
+};
+
+#define pcmk__set_client_flags(client, flags_to_set) do { \
+ (client)->flags = pcmk__set_flags_as(__func__, __LINE__, \
+ LOG_TRACE, \
+ "Client", pcmk__client_name(client), \
+ (client)->flags, (flags_to_set), #flags_to_set); \
+ } while (0)
+
+#define pcmk__clear_client_flags(client, flags_to_clear) do { \
+ (client)->flags = pcmk__clear_flags_as(__func__, __LINE__, \
+ LOG_TRACE, \
+ "Client", pcmk__client_name(client), \
+ (client)->flags, (flags_to_clear), #flags_to_clear); \
+ } while (0)
+
+#define pcmk__set_ipc_flags(ipc_flags, ipc_name, flags_to_set) do { \
+ ipc_flags = pcmk__set_flags_as(__func__, __LINE__, LOG_TRACE, \
+ "IPC", (ipc_name), \
+ (ipc_flags), (flags_to_set), \
+ #flags_to_set); \
+ } while (0)
+
+#define pcmk__clear_ipc_flags(ipc_flags, ipc_name, flags_to_clear) do { \
+ ipc_flags = pcmk__clear_flags_as(__func__, __LINE__, LOG_TRACE, \
+ "IPC", (ipc_name), \
+ (ipc_flags), (flags_to_clear), \
+ #flags_to_clear); \
+ } while (0)
+
+guint pcmk__ipc_client_count(void);
+void pcmk__foreach_ipc_client(GHFunc func, gpointer user_data);
+
+void pcmk__client_cleanup(void);
+
+pcmk__client_t *pcmk__find_client(const qb_ipcs_connection_t *c);
+pcmk__client_t *pcmk__find_client_by_id(const char *id);
+const char *pcmk__client_name(const pcmk__client_t *c);
+const char *pcmk__client_type_str(uint64_t client_type);
+
+pcmk__client_t *pcmk__new_unauth_client(void *key);
+pcmk__client_t *pcmk__new_client(qb_ipcs_connection_t *c, uid_t uid, gid_t gid);
+void pcmk__free_client(pcmk__client_t *c);
+void pcmk__drop_all_clients(qb_ipcs_service_t *s);
+bool pcmk__set_client_queue_max(pcmk__client_t *client, const char *qmax);
+
+xmlNode *pcmk__ipc_create_ack_as(const char *function, int line, uint32_t flags,
+ const char *tag, const char *ver, crm_exit_t status);
+#define pcmk__ipc_create_ack(flags, tag, ver, st) \
+ pcmk__ipc_create_ack_as(__func__, __LINE__, (flags), (tag), (ver), (st))
+
+int pcmk__ipc_send_ack_as(const char *function, int line, pcmk__client_t *c,
+ uint32_t request, uint32_t flags, const char *tag,
+ const char *ver, crm_exit_t status);
+#define pcmk__ipc_send_ack(c, req, flags, tag, ver, st) \
+ pcmk__ipc_send_ack_as(__func__, __LINE__, (c), (req), (flags), (tag), (ver), (st))
+
+int pcmk__ipc_prepare_iov(uint32_t request, xmlNode *message,
+ uint32_t max_send_size,
+ struct iovec **result, ssize_t *bytes);
+int pcmk__ipc_send_xml(pcmk__client_t *c, uint32_t request, xmlNode *message,
+ uint32_t flags);
+int pcmk__ipc_send_iov(pcmk__client_t *c, struct iovec *iov, uint32_t flags);
+xmlNode *pcmk__client_data2xml(pcmk__client_t *c, void *data,
+ uint32_t *id, uint32_t *flags);
+
+int pcmk__client_pid(qb_ipcs_connection_t *c);
+
+void pcmk__serve_attrd_ipc(qb_ipcs_service_t **ipcs,
+ struct qb_ipcs_service_handlers *cb);
+void pcmk__serve_fenced_ipc(qb_ipcs_service_t **ipcs,
+ struct qb_ipcs_service_handlers *cb);
+void pcmk__serve_pacemakerd_ipc(qb_ipcs_service_t **ipcs,
+ struct qb_ipcs_service_handlers *cb);
+qb_ipcs_service_t *pcmk__serve_schedulerd_ipc(struct qb_ipcs_service_handlers *cb);
+qb_ipcs_service_t *pcmk__serve_controld_ipc(struct qb_ipcs_service_handlers *cb);
+
+void pcmk__serve_based_ipc(qb_ipcs_service_t **ipcs_ro,
+ qb_ipcs_service_t **ipcs_rw,
+ qb_ipcs_service_t **ipcs_shm,
+ struct qb_ipcs_service_handlers *ro_cb,
+ struct qb_ipcs_service_handlers *rw_cb);
+
+void pcmk__stop_based_ipc(qb_ipcs_service_t *ipcs_ro,
+ qb_ipcs_service_t *ipcs_rw,
+ qb_ipcs_service_t *ipcs_shm);
+
+static inline const char *
+pcmk__ipc_sys_name(const char *ipc_name, const char *fallback)
+{
+ return ipc_name ? ipc_name : ((crm_system_name ? crm_system_name : fallback));
+}
+
+const char *pcmk__pcmkd_state_enum2friendly(enum pcmk_pacemakerd_state state);
+
+const char *pcmk__controld_api_reply2str(enum pcmk_controld_api_reply reply);
+const char *pcmk__pcmkd_api_reply2str(enum pcmk_pacemakerd_api_reply reply);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/crm/common/ipc_pacemakerd.h b/include/crm/common/ipc_pacemakerd.h
new file mode 100644
index 0000000..340f9a6
--- /dev/null
+++ b/include/crm/common/ipc_pacemakerd.h
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2020 the Pacemaker project contributors
+ *
+ * The version control history for this file may have further details.
+ *
+ * This source code is licensed under the GNU Lesser General Public License
+ * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
+ */
+
+#ifndef PCMK__CRM_COMMON_IPC_PACEMAKERD__H
+# define PCMK__CRM_COMMON_IPC_PACEMAKERD__H
+
+#include <sys/types.h> // time_t
+#include <crm/common/ipc.h> // pcmk_ipc_api_t
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * \file
+ * \brief IPC commands for Pacemakerd
+ *
+ * \ingroup core
+ */
+
+enum pcmk_pacemakerd_state {
+ pcmk_pacemakerd_state_invalid = -1,
+ pcmk_pacemakerd_state_init = 0,
+ pcmk_pacemakerd_state_starting_daemons,
+ pcmk_pacemakerd_state_wait_for_ping,
+ pcmk_pacemakerd_state_running,
+ pcmk_pacemakerd_state_shutting_down,
+ pcmk_pacemakerd_state_shutdown_complete,
+ pcmk_pacemakerd_state_remote,
+ pcmk_pacemakerd_state_max = pcmk_pacemakerd_state_remote,
+};
+
+//! Possible types of pacemakerd replies
+enum pcmk_pacemakerd_api_reply {
+ pcmk_pacemakerd_reply_unknown,
+ pcmk_pacemakerd_reply_ping,
+ pcmk_pacemakerd_reply_shutdown,
+};
+
+/*!
+ * Pacemakerd reply passed to event callback
+ */
+typedef struct {
+ enum pcmk_pacemakerd_api_reply reply_type;
+
+ union {
+ // pcmk_pacemakerd_reply_ping
+ struct {
+ const char *sys_from;
+ enum pcmk_pacemakerd_state state;
+ time_t last_good;
+ int status;
+ } ping;
+ // pcmk_pacemakerd_reply_shutdown
+ struct {
+ int status;
+ } shutdown;
+ } data;
+} pcmk_pacemakerd_api_reply_t;
+
+int pcmk_pacemakerd_api_ping(pcmk_ipc_api_t *api, const char *ipc_name);
+int pcmk_pacemakerd_api_shutdown(pcmk_ipc_api_t *api, const char *ipc_name);
+
+enum pcmk_pacemakerd_state
+ pcmk_pacemakerd_api_daemon_state_text2enum(const char *state);
+const char
+ *pcmk_pacemakerd_api_daemon_state_enum2text(enum pcmk_pacemakerd_state state);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // PCMK__CRM_COMMON_IPC_PACEMAKERD__H
diff --git a/include/crm/common/ipc_schedulerd.h b/include/crm/common/ipc_schedulerd.h
new file mode 100644
index 0000000..303ec59
--- /dev/null
+++ b/include/crm/common/ipc_schedulerd.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2021-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 PCMK__CRM_COMMON_IPC_SCHEDULERD__H
+# define PCMK__CRM_COMMON_IPC_SCHEDULERD__H
+
+#include <crm/common/ipc.h> // pcmk_ipc_api_t
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * \file
+ * \brief IPC commands for Schedulerd
+ *
+ * \ingroup core
+ */
+
+
+//! Possible types of schedulerd replies
+enum pcmk_schedulerd_api_reply {
+ pcmk_schedulerd_reply_unknown,
+ pcmk_schedulerd_reply_graph,
+};
+
+/*!
+ * Schedulerd reply passed to event callback
+ */
+typedef struct {
+ enum pcmk_schedulerd_api_reply reply_type;
+
+ union {
+ // pcmk__schedulerd_reply_graph
+ struct {
+ xmlNode *tgraph;
+ const char *reference;
+ const char *input;
+ } graph;
+ } data;
+} pcmk_schedulerd_api_reply_t;
+
+/*!
+ * \brief Make an IPC request to the scheduler for the transition graph
+ *
+ * \param[in,out] api IPC API connection
+ * \param[in] cib The CIB to create a transition graph for
+ * \param[out] ref The reference ID a response will have
+ *
+ * \return Standard Pacemaker return code
+ */
+int pcmk_schedulerd_api_graph(pcmk_ipc_api_t *api, xmlNode *cib, char **ref);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // PCMK__CRM_COMMON_IPC_SCHEDULERD__H
diff --git a/include/crm/common/iso8601.h b/include/crm/common/iso8601.h
new file mode 100644
index 0000000..78f530b
--- /dev/null
+++ b/include/crm/common/iso8601.h
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2005-2020 the Pacemaker project contributors
+ *
+ * The version control history for this file may have further details.
+ *
+ * This source code is licensed under the GNU Lesser General Public License
+ * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
+ */
+
+#ifndef PCMK__CRM_COMMON_ISO8601__H
+# define PCMK__CRM_COMMON_ISO8601__H
+
+# include <time.h>
+# include <ctype.h>
+# include <stdint.h> // uint32_t
+# include <stdbool.h> // bool
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * \file
+ * \brief ISO_8601 Date handling
+ * \ingroup date
+ */
+
+/*
+ * See https://en.wikipedia.org/wiki/ISO_8601
+ */
+
+typedef struct crm_time_s crm_time_t;
+
+typedef struct crm_time_period_s {
+ crm_time_t *start;
+ crm_time_t *end;
+ crm_time_t *diff;
+} crm_time_period_t;
+
+/* Creates a new date/time object conforming to ISO 8601, for example:
+ * Ordinal: 2010-01 12:00:00 +10:00
+ * Gregorian: 2010-01-01 12:00:00 +10:00
+ * ISO Week: 2010-W53-6 12:00:00 +10:00
+ *
+ * Notes:
+ * Only one of date, time is required
+ * If date or timezone is unspecified, they default to the current one
+ * Supplying NULL results in the current date/time
+ * Dashes may be omitted from dates
+ * Colons may be omitted from times and timezones
+ * A timezone of 'Z' denotes UTC time
+ */
+crm_time_t *crm_time_new(const char *string);
+crm_time_t *crm_time_new_undefined(void);
+void crm_time_free(crm_time_t * dt);
+
+bool crm_time_is_defined(const crm_time_t *t);
+char *crm_time_as_string(const crm_time_t *dt, int flags);
+
+#define crm_time_log(level, prefix, dt, flags) \
+ crm_time_log_alias(level, __FILE__, __func__, __LINE__, prefix, dt, flags)
+
+void crm_time_log_alias(int log_level, const char *file, const char *function,
+ int line, const char *prefix,
+ const crm_time_t *date_time, int flags);
+
+# define crm_time_log_date 0x001
+# define crm_time_log_timeofday 0x002
+# define crm_time_log_with_timezone 0x004
+# define crm_time_log_duration 0x008
+
+# define crm_time_ordinal 0x010
+# define crm_time_weeks 0x020
+# define crm_time_seconds 0x100
+# define crm_time_epoch 0x200
+# define crm_time_usecs 0x400
+
+crm_time_t *crm_time_parse_duration(const char *duration_str);
+crm_time_t *crm_time_calculate_duration(const crm_time_t *dt,
+ const crm_time_t *value);
+crm_time_period_t *crm_time_parse_period(const char *period_str);
+void crm_time_free_period(crm_time_period_t *period);
+
+int crm_time_compare(const crm_time_t *a, const crm_time_t *b);
+
+int crm_time_get_timeofday(const crm_time_t *dt, uint32_t *h, uint32_t *m,
+ uint32_t *s);
+int crm_time_get_timezone(const crm_time_t *dt, uint32_t *h, uint32_t *m);
+int crm_time_get_gregorian(const crm_time_t *dt, uint32_t *y, uint32_t *m,
+ uint32_t *d);
+int crm_time_get_ordinal(const crm_time_t *dt, uint32_t *y, uint32_t *d);
+int crm_time_get_isoweek(const crm_time_t *dt, uint32_t *y, uint32_t *w,
+ uint32_t * d);
+
+/* Time in seconds since 0000-01-01 00:00:00Z */
+long long crm_time_get_seconds(const crm_time_t *dt);
+
+/* Time in seconds since 1970-01-01 00:00:00Z */
+long long crm_time_get_seconds_since_epoch(const crm_time_t *dt);
+
+void crm_time_set(crm_time_t *target, const crm_time_t *source);
+void crm_time_set_timet(crm_time_t *target, const time_t *source);
+
+/* Returns a new time object */
+crm_time_t *pcmk_copy_time(const crm_time_t *source);
+crm_time_t *crm_time_add(const crm_time_t *dt, const crm_time_t *value);
+crm_time_t *crm_time_subtract(const crm_time_t *dt, const crm_time_t *value);
+
+/* All crm_time_add_... functions support negative values */
+void crm_time_add_seconds(crm_time_t * dt, int value);
+void crm_time_add_minutes(crm_time_t * dt, int value);
+void crm_time_add_hours(crm_time_t * dt, int value);
+void crm_time_add_days(crm_time_t * dt, int value);
+void crm_time_add_weeks(crm_time_t * dt, int value);
+void crm_time_add_months(crm_time_t * dt, int value);
+void crm_time_add_years(crm_time_t * dt, int value);
+
+/* Useful helper functions */
+int crm_time_january1_weekday(int year);
+int crm_time_weeks_in_year(int year);
+int crm_time_days_in_month(int month, int year);
+
+bool crm_time_leapyear(int year);
+bool crm_time_check(const crm_time_t *dt);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/crm/common/iso8601_internal.h b/include/crm/common/iso8601_internal.h
new file mode 100644
index 0000000..f924d8a
--- /dev/null
+++ b/include/crm/common/iso8601_internal.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2015-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 PCMK__ISO8601_INTERNAL__H
+# define PCMK__ISO8601_INTERNAL__H
+
+#include <time.h>
+#include <sys/time.h>
+#include <ctype.h>
+#include <crm/common/iso8601.h>
+
+typedef struct pcmk__time_us pcmk__time_hr_t;
+
+pcmk__time_hr_t *pcmk__time_hr_convert(pcmk__time_hr_t *target,
+ const crm_time_t *dt);
+void pcmk__time_set_hr_dt(crm_time_t *target, const pcmk__time_hr_t *hr_dt);
+pcmk__time_hr_t *pcmk__time_hr_now(time_t *epoch);
+pcmk__time_hr_t *pcmk__time_hr_new(const char *date_time);
+void pcmk__time_hr_free(pcmk__time_hr_t *hr_dt);
+char *pcmk__time_format_hr(const char *format, const pcmk__time_hr_t *hr_dt);
+char *pcmk__epoch2str(const time_t *source, uint32_t flags);
+char *pcmk__timespec2str(const struct timespec *ts, uint32_t flags);
+const char *pcmk__readable_interval(guint interval_ms);
+crm_time_t *pcmk__copy_timet(time_t source);
+
+struct pcmk__time_us {
+ int years;
+ int months; /* Only for durations */
+ int days;
+ int seconds;
+ int offset; /* Seconds */
+ bool duration;
+ int useconds;
+};
+
+#endif
diff --git a/include/crm/common/lists_internal.h b/include/crm/common/lists_internal.h
new file mode 100644
index 0000000..1de695a
--- /dev/null
+++ b/include/crm/common/lists_internal.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2020-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 PCMK__LISTS_INTERNAL__H
+#define PCMK__LISTS_INTERNAL__H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <glib.h>
+
+/*!
+ * \internal
+ * \brief Return the list that is \p from - \p items
+ *
+ * \param[in] from Source list
+ * \param[in] items List containing items to remove from \p from
+ * \param[in] cmp Function used to compare list elements
+ *
+ * \return Newly allocated list
+ */
+GList *pcmk__subtract_lists(GList *from, const GList *items,
+ GCompareFunc cmp);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/crm/common/logging.h b/include/crm/common/logging.h
new file mode 100644
index 0000000..2878fba
--- /dev/null
+++ b/include/crm/common/logging.h
@@ -0,0 +1,411 @@
+/*
+ * Copyright 2004-2023 the Pacemaker project contributors
+ *
+ * The version control history for this file may have further details.
+ *
+ * This source code is licensed under the GNU General Public License version 2
+ * or later (GPLv2+) WITHOUT ANY WARRANTY.
+ */
+
+#ifndef PCMK__CRM_COMMON_LOGGING__H
+# define PCMK__CRM_COMMON_LOGGING__H
+
+# include <stdio.h>
+# include <glib.h>
+# include <qb/qblog.h>
+# include <libxml/tree.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * \file
+ * \brief Wrappers for and extensions to libqb logging
+ * \ingroup core
+ */
+
+
+/* Define custom log priorities.
+ *
+ * syslog(3) uses int for priorities, but libqb's struct qb_log_callsite uses
+ * uint8_t, so make sure they fit in the latter.
+ */
+
+// Define something even less desired than debug
+# ifndef LOG_TRACE
+# define LOG_TRACE (LOG_DEBUG+1)
+# endif
+
+// Print message to stdout instead of logging it
+# ifndef LOG_STDOUT
+# define LOG_STDOUT 254
+# endif
+
+// Don't send message anywhere
+# ifndef LOG_NEVER
+# define LOG_NEVER 255
+# endif
+
+/* "Extended information" logging support */
+#ifdef QB_XS
+# define CRM_XS QB_XS
+# define crm_extended_logging(t, e) qb_log_ctl((t), QB_LOG_CONF_EXTENDED, (e))
+#else
+# define CRM_XS "|"
+
+/* A caller might want to check the return value, so we can't define this as a
+ * no-op, and we can't simply define it to be 0 because gcc will then complain
+ * when the value isn't checked.
+ */
+static inline int
+crm_extended_logging(int t, int e)
+{
+ return 0;
+}
+#endif
+
+extern unsigned int crm_log_level;
+extern unsigned int crm_trace_nonlog;
+
+/*! \deprecated Pacemaker library functions set this when a configuration
+ * error is found, which turns on extra messages at the end of
+ * processing. It should not be used directly and will be removed
+ * from the public C API in a future release.
+ */
+extern gboolean crm_config_error;
+
+/*! \deprecated Pacemaker library functions set this when a configuration
+ * warning is found, which turns on extra messages at the end of
+ * processing. It should not be used directly and will be removed
+ * from the public C API in a future release.
+ */
+extern gboolean crm_config_warning;
+
+void crm_enable_blackbox(int nsig);
+void crm_disable_blackbox(int nsig);
+void crm_write_blackbox(int nsig, const struct qb_log_callsite *callsite);
+
+void crm_update_callsites(void);
+
+void crm_log_deinit(void);
+
+/*!
+ * \brief Initializes the logging system and defaults to the least verbose output level
+ *
+ * \param[in] entity If not NULL, will be used as the identity for logging purposes
+ * \param[in] argc The number of command line parameters
+ * \param[in] argv The command line parameter values
+ */
+void crm_log_preinit(const char *entity, int argc, char *const *argv);
+gboolean crm_log_init(const char *entity, uint8_t level, gboolean daemon,
+ gboolean to_stderr, int argc, char **argv, gboolean quiet);
+
+void crm_log_args(int argc, char **argv);
+void crm_log_output_fn(const char *file, const char *function, int line, int level,
+ const char *prefix, const char *output);
+
+// Log a block of text line by line
+#define crm_log_output(level, prefix, output) \
+ crm_log_output_fn(__FILE__, __func__, __LINE__, level, prefix, output)
+
+void crm_bump_log_level(int argc, char **argv);
+
+void crm_enable_stderr(int enable);
+
+gboolean crm_is_callsite_active(struct qb_log_callsite *cs, uint8_t level, uint32_t tags);
+
+/* returns the old value */
+unsigned int set_crm_log_level(unsigned int level);
+
+unsigned int get_crm_log_level(void);
+
+void pcmk_log_xml_impl(uint8_t level, const char *text, const xmlNode *xml);
+
+/*
+ * Throughout the macros below, note the leading, pre-comma, space in the
+ * various ' , ##args' occurrences to aid portability across versions of 'gcc'.
+ * https://gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html#Variadic-Macros
+ */
+#if defined(__clang__)
+# define CRM_TRACE_INIT_DATA(name)
+# else
+# include <assert.h> // required by QB_LOG_INIT_DATA() macro
+# define CRM_TRACE_INIT_DATA(name) QB_LOG_INIT_DATA(name)
+#endif
+
+/*!
+ * \internal
+ * \brief Clip log_level to \p uint8_t range
+ *
+ * \param[in] level Log level to clip
+ *
+ * \return 0 if \p level is less than 0, \p UINT8_MAX if \p level is greater
+ * than \p UINT8_MAX, or \p level otherwise
+ */
+/* @COMPAT: Make this function internal at a compatibility break. It's used in
+ * public macros for now.
+ */
+static inline uint8_t
+pcmk__clip_log_level(int level)
+{
+ if (level <= 0) {
+ return 0;
+ }
+ if (level >= UINT8_MAX) {
+ return UINT8_MAX;
+ }
+ return level;
+}
+
+/* Using "switch" instead of "if" in these macro definitions keeps
+ * static analysis from complaining about constant evaluations
+ */
+
+/*!
+ * \brief Log a message
+ *
+ * \param[in] level Priority at which to log the message
+ * \param[in] fmt printf-style format string literal for message
+ * \param[in] args Any arguments needed by format string
+ */
+# define do_crm_log(level, fmt, args...) do { \
+ uint8_t _level = pcmk__clip_log_level(level); \
+ \
+ switch (_level) { \
+ case LOG_STDOUT: \
+ printf(fmt "\n" , ##args); \
+ break; \
+ case LOG_NEVER: \
+ break; \
+ default: \
+ qb_log_from_external_source(__func__, __FILE__, fmt, \
+ _level, __LINE__, 0 , ##args); \
+ break; \
+ } \
+ } while (0)
+
+/*!
+ * \brief Log a message that is likely to be filtered out
+ *
+ * \param[in] level Priority at which to log the message
+ * \param[in] fmt printf-style format string for message
+ * \param[in] args Any arguments needed by format string
+ *
+ * \note This does nothing when level is \p LOG_STDOUT.
+ */
+# define do_crm_log_unlikely(level, fmt, args...) do { \
+ uint8_t _level = pcmk__clip_log_level(level); \
+ \
+ switch (_level) { \
+ case LOG_STDOUT: case LOG_NEVER: \
+ break; \
+ default: { \
+ static struct qb_log_callsite *trace_cs = NULL; \
+ if (trace_cs == NULL) { \
+ trace_cs = qb_log_callsite_get(__func__, __FILE__, fmt, \
+ _level, __LINE__, 0); \
+ } \
+ if (crm_is_callsite_active(trace_cs, _level, 0)) { \
+ qb_log_from_external_source(__func__, __FILE__, fmt, \
+ _level, __LINE__, 0 , \
+ ##args); \
+ } \
+ } \
+ break; \
+ } \
+ } while (0)
+
+# define CRM_LOG_ASSERT(expr) do { \
+ if (!(expr)) { \
+ static struct qb_log_callsite *core_cs = NULL; \
+ if(core_cs == NULL) { \
+ core_cs = qb_log_callsite_get(__func__, __FILE__, \
+ "log-assert", LOG_TRACE, \
+ __LINE__, 0); \
+ } \
+ crm_abort(__FILE__, __func__, __LINE__, #expr, \
+ core_cs?core_cs->targets:FALSE, TRUE); \
+ } \
+ } while(0)
+
+/* 'failure_action' MUST NOT be 'continue' as it will apply to the
+ * macro's do-while loop
+ */
+# define CRM_CHECK(expr, failure_action) do { \
+ if (!(expr)) { \
+ static struct qb_log_callsite *core_cs = NULL; \
+ if (core_cs == NULL) { \
+ core_cs = qb_log_callsite_get(__func__, __FILE__, \
+ "check-assert", \
+ LOG_TRACE, __LINE__, 0); \
+ } \
+ crm_abort(__FILE__, __func__, __LINE__, #expr, \
+ (core_cs? core_cs->targets: FALSE), TRUE); \
+ failure_action; \
+ } \
+ } while(0)
+
+/*!
+ * \brief Log XML line-by-line in a formatted fashion
+ *
+ * \param[in] level Priority at which to log the messages
+ * \param[in] text Prefix for each line
+ * \param[in] xml XML to log
+ *
+ * \note This does nothing when \p level is \p LOG_STDOUT.
+ */
+# define do_crm_log_xml(level, text, xml) do { \
+ uint8_t _level = pcmk__clip_log_level(level); \
+ static struct qb_log_callsite *xml_cs = NULL; \
+ \
+ switch (_level) { \
+ case LOG_STDOUT: \
+ case LOG_NEVER: \
+ break; \
+ default: \
+ if (xml_cs == NULL) { \
+ xml_cs = qb_log_callsite_get(__func__, __FILE__, \
+ "xml-blob", _level, \
+ __LINE__, 0); \
+ } \
+ if (crm_is_callsite_active(xml_cs, _level, 0)) { \
+ pcmk_log_xml_impl(_level, text, xml); \
+ } \
+ break; \
+ } \
+ } while(0)
+
+/*!
+ * \brief Log a message as if it came from a different code location
+ *
+ * \param[in] level Priority at which to log the message
+ * \param[in] file Source file name to use instead of __FILE__
+ * \param[in] function Source function name to use instead of __func__
+ * \param[in] line Source line number to use instead of __line__
+ * \param[in] fmt printf-style format string literal for message
+ * \param[in] args Any arguments needed by format string
+ */
+# define do_crm_log_alias(level, file, function, line, fmt, args...) do { \
+ uint8_t _level = pcmk__clip_log_level(level); \
+ \
+ switch (_level) { \
+ case LOG_STDOUT: \
+ printf(fmt "\n" , ##args); \
+ break; \
+ case LOG_NEVER: \
+ break; \
+ default: \
+ qb_log_from_external_source(function, file, fmt, _level, \
+ line, 0 , ##args); \
+ break; \
+ } \
+ } while (0)
+
+/*!
+ * \brief Send a system error message to both the log and stderr
+ *
+ * \param[in] level Priority at which to log the message
+ * \param[in] fmt printf-style format string for message
+ * \param[in] args Any arguments needed by format string
+ *
+ * \deprecated One of the other logging functions should be used with
+ * pcmk_strerror() instead.
+ * \note This is a macro, and \p level may be evaluated more than once.
+ * \note Because crm_perror() adds the system error message and error number
+ * onto the end of fmt, that information will become extended information
+ * if CRM_XS is used inside fmt and will not show up in syslog.
+ */
+# define crm_perror(level, fmt, args...) do { \
+ uint8_t _level = pcmk__clip_log_level(level); \
+ \
+ switch (_level) { \
+ case LOG_NEVER: \
+ break; \
+ default: { \
+ const char *err = strerror(errno); \
+ if (_level <= crm_log_level) { \
+ fprintf(stderr, fmt ": %s (%d)\n" , ##args, err, \
+ errno); \
+ } \
+ /* Pass original level arg since do_crm_log() also declares \
+ * _level \
+ */ \
+ do_crm_log((level), fmt ": %s (%d)" , ##args, err, errno); \
+ } \
+ break; \
+ } \
+ } while (0)
+
+/*!
+ * \brief Log a message with a tag (for use with PCMK_trace_tags)
+ *
+ * \param[in] level Priority at which to log the message
+ * \param[in] tag String to tag message with
+ * \param[in] fmt printf-style format string for message
+ * \param[in] args Any arguments needed by format string
+ *
+ * \note This does nothing when level is LOG_STDOUT.
+ */
+# define crm_log_tag(level, tag, fmt, args...) do { \
+ uint8_t _level = pcmk__clip_log_level(level); \
+ \
+ switch (_level) { \
+ case LOG_STDOUT: case LOG_NEVER: \
+ break; \
+ default: { \
+ static struct qb_log_callsite *trace_tag_cs = NULL; \
+ int converted_tag = g_quark_try_string(tag); \
+ if (trace_tag_cs == NULL) { \
+ trace_tag_cs = qb_log_callsite_get(__func__, __FILE__, \
+ fmt, _level, \
+ __LINE__, \
+ converted_tag); \
+ } \
+ if (crm_is_callsite_active(trace_tag_cs, _level, \
+ converted_tag)) { \
+ qb_log_from_external_source(__func__, __FILE__, fmt, \
+ _level, __LINE__, \
+ converted_tag , ##args); \
+ } \
+ } \
+ } \
+ } while (0)
+
+# define crm_emerg(fmt, args...) qb_log(LOG_EMERG, fmt , ##args)
+# define crm_crit(fmt, args...) qb_logt(LOG_CRIT, 0, fmt , ##args)
+# define crm_err(fmt, args...) qb_logt(LOG_ERR, 0, fmt , ##args)
+# define crm_warn(fmt, args...) qb_logt(LOG_WARNING, 0, fmt , ##args)
+# define crm_notice(fmt, args...) qb_logt(LOG_NOTICE, 0, fmt , ##args)
+# define crm_info(fmt, args...) qb_logt(LOG_INFO, 0, fmt , ##args)
+
+# define crm_debug(fmt, args...) do_crm_log_unlikely(LOG_DEBUG, fmt , ##args)
+# define crm_trace(fmt, args...) do_crm_log_unlikely(LOG_TRACE, fmt , ##args)
+
+# define crm_log_xml_crit(xml, text) do_crm_log_xml(LOG_CRIT, text, xml)
+# define crm_log_xml_err(xml, text) do_crm_log_xml(LOG_ERR, text, xml)
+# define crm_log_xml_warn(xml, text) do_crm_log_xml(LOG_WARNING, text, xml)
+# define crm_log_xml_notice(xml, text) do_crm_log_xml(LOG_NOTICE, text, xml)
+# define crm_log_xml_info(xml, text) do_crm_log_xml(LOG_INFO, text, xml)
+# define crm_log_xml_debug(xml, text) do_crm_log_xml(LOG_DEBUG, text, xml)
+# define crm_log_xml_trace(xml, text) do_crm_log_xml(LOG_TRACE, text, xml)
+
+# define crm_log_xml_explicit(xml, text) do { \
+ static struct qb_log_callsite *digest_cs = NULL; \
+ digest_cs = qb_log_callsite_get( \
+ __func__, __FILE__, text, LOG_TRACE, __LINE__, \
+ crm_trace_nonlog); \
+ if (digest_cs && digest_cs->targets) { \
+ do_crm_log_xml(LOG_TRACE, text, xml); \
+ } \
+ } while(0)
+
+#if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1)
+#include <crm/common/logging_compat.h>
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/crm/common/logging_compat.h b/include/crm/common/logging_compat.h
new file mode 100644
index 0000000..cfdb562
--- /dev/null
+++ b/include/crm/common/logging_compat.h
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2004-2023 the Pacemaker project contributors
+ *
+ * The version control history for this file may have further details.
+ *
+ * This source code is licensed under the GNU General Public License version 2
+ * or later (GPLv2+) WITHOUT ANY WARRANTY.
+ */
+
+#ifndef PCMK__CRM_COMMON_LOGGING_COMPAT__H
+# define PCMK__CRM_COMMON_LOGGING_COMPAT__H
+
+#include <glib.h>
+#include <libxml/tree.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * \file
+ * \brief Deprecated Pacemaker logging API
+ * \ingroup core
+ * \deprecated Do not include this header directly. Do not use Pacemaker
+ * libraries for general-purpose logging; libqb's logging API is a
+ * suitable replacement. The logging APIs in this header, and the
+ * header itself, will be removed in a future release.
+ */
+
+//! \deprecated This enum will be removed in a future release
+enum xml_log_options {
+ xml_log_option_filtered = 0x0001,
+ xml_log_option_formatted = 0x0002,
+ xml_log_option_text = 0x0004,
+ xml_log_option_full_fledged = 0x0008,
+ xml_log_option_diff_plus = 0x0010,
+ xml_log_option_diff_minus = 0x0020,
+ xml_log_option_diff_short = 0x0040,
+ xml_log_option_diff_all = 0x0100,
+ xml_log_option_dirty_add = 0x1000,
+ xml_log_option_open = 0x2000,
+ xml_log_option_children = 0x4000,
+ xml_log_option_close = 0x8000,
+};
+
+/*!
+ * \brief Log a message using constant priority
+ *
+ * \param[in] level Priority at which to log the message
+ * \param[in] fmt printf-style format string literal for message
+ * \param[in] args Any arguments needed by format string
+ *
+ * \deprecated Do not use Pacemaker for general-purpose logging
+ * \note This is a macro, and \p level may be evaluated more than once.
+ * This does nothing when level is LOG_STDOUT.
+ */
+# define do_crm_log_always(level, fmt, args...) do { \
+ switch (level) { \
+ case LOG_STDOUT: case LOG_NEVER: \
+ break; \
+ default: \
+ qb_log((level), fmt , ##args); \
+ break; \
+ } \
+ } while (0)
+
+//! \deprecated Do not use Pacemaker for general-purpose string handling
+#define crm_str(x) (const char *) ((x)? (x) : "<null>")
+
+//! \deprecated Do not use Pacemaker for general-purpose logging
+gboolean crm_log_cli_init(const char *entity);
+
+//! \deprecated Do not use Pacemaker for general-purpose logging
+gboolean crm_add_logfile(const char *filename);
+
+//! \deprecated Do not use Pacemaker for general-purpose logging
+void log_data_element(int log_level, const char *file, const char *function,
+ int line, const char *prefix, const xmlNode *data,
+ int depth, int legacy_options);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // PCMK__CRM_COMMON_LOGGING_COMPAT__H
diff --git a/include/crm/common/logging_internal.h b/include/crm/common/logging_internal.h
new file mode 100644
index 0000000..479dcab
--- /dev/null
+++ b/include/crm/common/logging_internal.h
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2015-2023 the Pacemaker project contributors
+ *
+ * The version control history for this file may have further details.
+ *
+ * This source code is licensed under the GNU General Public License version 2
+ * or later (GPLv2+) WITHOUT ANY WARRANTY.
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef PCMK__LOGGING_INTERNAL_H
+# define PCMK__LOGGING_INTERNAL_H
+
+# include <glib.h>
+
+# include <crm/common/logging.h>
+# include <crm/common/output_internal.h>
+
+/*!
+ * \internal
+ * \brief Log a configuration error
+ *
+ * \param[in] fmt printf(3)-style format string
+ * \param[in] ... Arguments for format string
+ */
+# define pcmk__config_err(fmt...) do { \
+ crm_config_error = TRUE; \
+ crm_err(fmt); \
+ } while (0)
+
+/*!
+ * \internal
+ * \brief Log a configuration warning
+ *
+ * \param[in] fmt printf(3)-style format string
+ * \param[in] ... Arguments for format string
+ */
+# define pcmk__config_warn(fmt...) do { \
+ crm_config_warning = TRUE; \
+ crm_warn(fmt); \
+ } while (0)
+
+/*!
+ * \internal
+ * \brief Execute code depending on whether trace logging is enabled
+ *
+ * This is similar to \p do_crm_log_unlikely() except instead of logging, it
+ * selects one of two code blocks to execute.
+ *
+ * \param[in] if_action Code block to execute if trace logging is enabled
+ * \param[in] else_action Code block to execute if trace logging is not enabled
+ *
+ * \note Neither \p if_action nor \p else_action can contain a \p break or
+ * \p continue statement.
+ */
+# define pcmk__if_tracing(if_action, else_action) do { \
+ static struct qb_log_callsite *trace_cs = NULL; \
+ \
+ if (trace_cs == NULL) { \
+ trace_cs = qb_log_callsite_get(__func__, __FILE__, \
+ "if_tracing", LOG_TRACE, \
+ __LINE__, crm_trace_nonlog); \
+ } \
+ if (crm_is_callsite_active(trace_cs, LOG_TRACE, \
+ crm_trace_nonlog)) { \
+ if_action; \
+ } else { \
+ else_action; \
+ } \
+ } while (0)
+
+/*!
+ * \internal
+ * \brief Initialize logging for command line tools
+ *
+ * \param[in] name The name of the program
+ * \param[in] verbosity How verbose to be in logging
+ *
+ * \note \p verbosity is not the same as the logging level (LOG_ERR, etc.).
+ */
+void pcmk__cli_init_logging(const char *name, unsigned int verbosity);
+
+int pcmk__add_logfile(const char *filename);
+void pcmk__add_logfiles(gchar **log_files, pcmk__output_t *out);
+
+void pcmk__free_common_logger(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/crm/common/mainloop.h b/include/crm/common/mainloop.h
new file mode 100644
index 0000000..a55bcdf
--- /dev/null
+++ b/include/crm/common/mainloop.h
@@ -0,0 +1,192 @@
+/*
+ * Copyright 2009-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 PCMK__CRM_COMMON_MAINLOOP__H
+# define PCMK__CRM_COMMON_MAINLOOP__H
+
+# include <signal.h> // sighandler_t
+# include <glib.h>
+# include <stdbool.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * \file
+ * \brief Wrappers for and extensions to glib mainloop
+ * \ingroup core
+ */
+
+enum mainloop_child_flags {
+ /* don't kill pid group on timeout, only kill the pid */
+ mainloop_leave_pid_group = 0x01,
+};
+
+typedef struct trigger_s crm_trigger_t;
+typedef struct mainloop_io_s mainloop_io_t;
+typedef struct mainloop_child_s mainloop_child_t;
+typedef struct mainloop_timer_s mainloop_timer_t;
+
+void mainloop_cleanup(void);
+
+crm_trigger_t *mainloop_add_trigger(int priority, int (*dispatch) (gpointer user_data),
+ gpointer userdata);
+
+void mainloop_set_trigger(crm_trigger_t * source);
+
+void mainloop_trigger_complete(crm_trigger_t * trig);
+
+gboolean mainloop_destroy_trigger(crm_trigger_t * source);
+
+# ifndef HAVE_SIGHANDLER_T
+typedef void (*sighandler_t)(int);
+# endif
+
+sighandler_t crm_signal_handler(int sig, sighandler_t dispatch);
+
+gboolean mainloop_add_signal(int sig, void (*dispatch) (int sig));
+
+gboolean mainloop_destroy_signal(int sig);
+
+bool mainloop_timer_running(mainloop_timer_t *t);
+
+void mainloop_timer_start(mainloop_timer_t *t);
+
+void mainloop_timer_stop(mainloop_timer_t *t);
+
+guint mainloop_timer_set_period(mainloop_timer_t *t, guint period_ms);
+
+mainloop_timer_t *mainloop_timer_add(const char *name, guint period_ms, bool repeat, GSourceFunc cb, void *userdata);
+
+void mainloop_timer_del(mainloop_timer_t *t);
+
+
+# include <crm/common/ipc.h>
+# include <qb/qbipcs.h>
+
+struct ipc_client_callbacks {
+ /*!
+ * \brief Dispatch function for an IPC connection used as mainloop source
+ *
+ * \param[in] buffer Message read from IPC connection
+ * \param[in] length Number of bytes in \p buffer
+ * \param[in] userdata User data passed when creating mainloop source
+ *
+ * \return Negative value to remove source, anything else to keep it
+ */
+ int (*dispatch) (const char *buffer, ssize_t length, gpointer userdata);
+
+ /*!
+ * \brief Destroy function for mainloop IPC connection client data
+ *
+ * \param[in,out] userdata User data passed when creating mainloop source
+ */
+ void (*destroy) (gpointer userdata);
+};
+
+qb_ipcs_service_t *mainloop_add_ipc_server(const char *name, enum qb_ipc_type type,
+ struct qb_ipcs_service_handlers *callbacks);
+
+/*!
+ * \brief Start server-side API end-point, hooked into the internal event loop
+ *
+ * \param[in] name name of the IPC end-point ("address" for the client)
+ * \param[in] type selects libqb's IPC back-end (or use #QB_IPC_NATIVE)
+ * \param[in] callbacks defines libqb's IPC service-level handlers
+ * \param[in] priority priority relative to other events handled in the
+ * abstract handling loop, use #QB_LOOP_MED when unsure
+ *
+ * \return libqb's opaque handle to the created service abstraction
+ *
+ * \note For portability concerns, do not use this function if you keep
+ * \p priority as #QB_LOOP_MED, stick with #mainloop_add_ipc_server
+ * (with exactly such semantics) instead (once you link with this new
+ * symbol employed, you can't downgrade the library freely anymore).
+ *
+ * \note The intended effect will only get fully reflected when run-time
+ * linked to patched libqb: https://github.com/ClusterLabs/libqb/pull/352
+ */
+qb_ipcs_service_t *mainloop_add_ipc_server_with_prio(const char *name,
+ enum qb_ipc_type type,
+ struct qb_ipcs_service_handlers *callbacks,
+ enum qb_loop_priority prio);
+
+void mainloop_del_ipc_server(qb_ipcs_service_t * server);
+
+mainloop_io_t *mainloop_add_ipc_client(const char *name, int priority, size_t max_size,
+ void *userdata, struct ipc_client_callbacks *callbacks);
+
+void mainloop_del_ipc_client(mainloop_io_t * client);
+
+crm_ipc_t *mainloop_get_ipc_client(mainloop_io_t * client);
+
+struct mainloop_fd_callbacks {
+ /*!
+ * \brief Dispatch function for mainloop file descriptor with data ready
+ *
+ * \param[in,out] userdata User data passed when creating mainloop source
+ *
+ * \return Negative value to remove source, anything else to keep it
+ */
+ int (*dispatch) (gpointer userdata);
+
+ /*!
+ * \brief Destroy function for mainloop file descriptor client data
+ *
+ * \param[in,out] userdata User data passed when creating mainloop source
+ */
+ void (*destroy) (gpointer userdata);
+};
+
+mainloop_io_t *mainloop_add_fd(const char *name, int priority, int fd, void *userdata,
+ struct mainloop_fd_callbacks *callbacks);
+
+void mainloop_del_fd(mainloop_io_t * client);
+
+/*
+ * Create a new tracked process
+ * To track a process group, use -pid
+ */
+void mainloop_child_add(pid_t pid,
+ int timeout,
+ const char *desc,
+ void *userdata,
+ void (*callback) (mainloop_child_t * p, pid_t pid, int core, int signo, int exitcode));
+
+void mainloop_child_add_with_flags(pid_t pid,
+ int timeout,
+ const char *desc,
+ void *userdata,
+ enum mainloop_child_flags,
+ void (*callback) (mainloop_child_t * p, pid_t pid, int core, int signo, int exitcode));
+
+void *mainloop_child_userdata(mainloop_child_t * child);
+int mainloop_child_timeout(mainloop_child_t * child);
+const char *mainloop_child_name(mainloop_child_t * child);
+
+pid_t mainloop_child_pid(mainloop_child_t * child);
+void mainloop_clear_child_userdata(mainloop_child_t * child);
+gboolean mainloop_child_kill(pid_t pid);
+
+void pcmk_quit_main_loop(GMainLoop *mloop, unsigned int n);
+void pcmk_drain_main_loop(GMainLoop *mloop, guint timer_ms,
+ bool (*check)(guint));
+
+# define G_PRIORITY_MEDIUM (G_PRIORITY_HIGH/2)
+
+#if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1)
+#include <crm/common/mainloop_compat.h>
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/crm/common/mainloop_compat.h b/include/crm/common/mainloop_compat.h
new file mode 100644
index 0000000..5eb445a
--- /dev/null
+++ b/include/crm/common/mainloop_compat.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2009-2021 the Pacemaker project contributors
+ *
+ * The version control history for this file may have further details.
+ *
+ * This source code is licensed under the GNU Lesser General Public License
+ * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
+ */
+
+#ifndef PCMK__CRM_COMMON_MAINLOOP_COMPAT__H
+# define PCMK__CRM_COMMON_MAINLOOP_COMPAT__H
+
+# include <glib.h>
+# include <stdbool.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * \file
+ * \brief Deprecated Pacemaker mainloop API
+ * \ingroup core
+ * \deprecated Do not include this header directly. The main loop APIs in this
+ * header, and the header itself, will be removed in a future
+ * release.
+ */
+
+//! \deprecated Use crm_signal_handler() instead
+gboolean crm_signal(int sig, void (*dispatch) (int sig));
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // PCMK__CRM_COMMON_MAINLOOP_COMPAT__H
diff --git a/include/crm/common/messages_internal.h b/include/crm/common/messages_internal.h
new file mode 100644
index 0000000..0d1908f
--- /dev/null
+++ b/include/crm/common/messages_internal.h
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2018-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 PCMK__CRM_COMMON_MESSAGES_INTERNAL__H
+#define PCMK__CRM_COMMON_MESSAGES_INTERNAL__H
+
+#include <stdint.h> // uint32_t
+#include <libxml/tree.h> // xmlNode
+#include <crm/common/ipc_internal.h> // pcmk__client_t
+#include <crm/common/results_internal.h> // pcmk__action_result_t
+
+enum pcmk__request_flags {
+ pcmk__request_none = UINT32_C(0),
+
+ /* It would be nice if we could check for synchronous requests generically,
+ * but each daemon uses its own call options, so the daemons are responsible
+ * for setting this flag when appropriate.
+ */
+ pcmk__request_sync = (UINT32_C(1) << 0),
+
+ /* Whether reply must use original call options (the library code does not
+ * use this, so it is for internal daemon use)
+ */
+ pcmk__request_reuse_options = (UINT32_C(1) << 1),
+};
+
+// Server request (whether from an IPC client or cluster peer)
+typedef struct {
+ // If request is from an IPC client
+ pcmk__client_t *ipc_client; // IPC client (NULL if not via IPC)
+ uint32_t ipc_id; // IPC message ID
+ uint32_t ipc_flags; // IPC message flags
+
+ // If message is from a cluster peer
+ const char *peer; // Peer name (NULL if not via cluster)
+
+ // Common information regardless of origin
+ xmlNode *xml; // Request XML
+ int call_options; // Call options set on request
+ uint32_t flags; // Flag group of pcmk__request_flags
+ pcmk__action_result_t result; // Where to store operation result
+
+ /* It would be nice if we could pull the IPC command from the XML
+ * generically, but each daemon uses a different XML attribute for it,
+ * so the daemon is responsible for populating this field.
+ *
+ * This must be a copy of the XML field, and not just a pointer into xml,
+ * because handlers might modify the original XML.
+ *
+ * @TODO Create a per-daemon struct with IPC handlers, IPC endpoints, etc.,
+ * and the name of the XML attribute for IPC commands, then replace this
+ * with a convenience function to copy the command.
+ */
+ char *op; // IPC command name
+} pcmk__request_t;
+
+#define pcmk__set_request_flags(request, flags_to_set) do { \
+ (request)->flags = pcmk__set_flags_as(__func__, __LINE__, \
+ LOG_TRACE, "Request", "message", (request)->flags, \
+ (flags_to_set), #flags_to_set); \
+ } while (0)
+
+// Type for mapping a server command to a handler
+typedef struct {
+ const char *command;
+ xmlNode *(*handler)(pcmk__request_t *request);
+} pcmk__server_command_t;
+
+const char *pcmk__message_name(const char *name);
+GHashTable *pcmk__register_handlers(const pcmk__server_command_t handlers[]);
+xmlNode *pcmk__process_request(pcmk__request_t *request, GHashTable *handlers);
+void pcmk__reset_request(pcmk__request_t *request);
+
+/*!
+ * \internal
+ * \brief Get a loggable description of a request's origin
+ *
+ * \param[in] request
+ *
+ * \return "peer" if request was via CPG, "client" if via IPC, or "originator"
+ * if unknown
+ */
+static inline const char *
+pcmk__request_origin_type(const pcmk__request_t *request)
+{
+ if ((request != NULL) && (request->ipc_client != NULL)) {
+ return "client";
+ } else if ((request != NULL) && (request->peer != NULL)) {
+ return "peer";
+ } else {
+ return "originator";
+ }
+}
+
+/*!
+ * \internal
+ * \brief Get a loggable name for a request's origin
+ *
+ * \param[in] request
+ *
+ * \return Peer name if request was via CPG, client name if via IPC, or
+ * "(unspecified)" if unknown
+ */
+static inline const char *
+pcmk__request_origin(const pcmk__request_t *request)
+{
+ if ((request != NULL) && (request->ipc_client != NULL)) {
+ return pcmk__client_name(request->ipc_client);
+ } else if ((request != NULL) && (request->peer != NULL)) {
+ return request->peer;
+ } else {
+ return "(unspecified)";
+ }
+}
+
+#endif // PCMK__CRM_COMMON_MESSAGES_INTERNAL__H
diff --git a/include/crm/common/nvpair.h b/include/crm/common/nvpair.h
new file mode 100644
index 0000000..aebc199
--- /dev/null
+++ b/include/crm/common/nvpair.h
@@ -0,0 +1,88 @@
+/*
+ * 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 PCMK__CRM_COMMON_NVPAIR__H
+# define PCMK__CRM_COMMON_NVPAIR__H
+
+# include <sys/time.h> // struct timeval
+# include <glib.h> // gpointer, gboolean, guint
+# include <libxml/tree.h> // xmlNode
+# include <crm/crm.h>
+
+
+# ifdef __cplusplus
+extern "C" {
+# endif
+
+/**
+ * \file
+ * \brief Functionality for manipulating name/value pairs
+ * \ingroup core
+ */
+
+typedef struct pcmk_nvpair_s {
+ char *name;
+ char *value;
+} pcmk_nvpair_t;
+
+GSList *pcmk_prepend_nvpair(GSList *nvpairs, const char *name, const char *value);
+void pcmk_free_nvpairs(GSList *nvpairs);
+GSList *pcmk_sort_nvpairs(GSList *list);
+GSList *pcmk_xml_attrs2nvpairs(const xmlNode *xml);
+void pcmk_nvpairs2xml_attrs(GSList *list, xmlNode *xml);
+
+xmlNode *crm_create_nvpair_xml(xmlNode *parent, const char *id,
+ const char *name, const char *value);
+void hash2nvpair(gpointer key, gpointer value, gpointer user_data);
+void hash2field(gpointer key, gpointer value, gpointer user_data);
+void hash2metafield(gpointer key, gpointer value, gpointer user_data);
+void hash2smartfield(gpointer key, gpointer value, gpointer user_data);
+GHashTable *xml2list(const xmlNode *parent);
+
+const char *crm_xml_add(xmlNode *node, const char *name, const char *value);
+const char *crm_xml_replace(xmlNode *node, const char *name, const char *value);
+const char *crm_xml_add_int(xmlNode *node, const char *name, int value);
+const char *crm_xml_add_ll(xmlNode *node, const char *name, long long value);
+const char *crm_xml_add_ms(xmlNode *node, const char *name, guint ms);
+const char *crm_xml_add_timeval(xmlNode *xml, const char *name_sec,
+ const char *name_usec,
+ const struct timeval *value);
+
+const char *crm_element_value(const xmlNode *data, const char *name);
+int crm_element_value_int(const xmlNode *data, const char *name, int *dest);
+int crm_element_value_ll(const xmlNode *data, const char *name, long long *dest);
+int crm_element_value_ms(const xmlNode *data, const char *name, guint *dest);
+int crm_element_value_epoch(const xmlNode *xml, const char *name, time_t *dest);
+int crm_element_value_timeval(const xmlNode *data, const char *name_sec,
+ const char *name_usec, struct timeval *dest);
+char *crm_element_value_copy(const xmlNode *data, const char *name);
+
+/*!
+ * \brief Copy an element from one XML object to another
+ *
+ * \param[in] obj1 Source XML
+ * \param[in,out] obj2 Destination XML
+ * \param[in] element Name of element to copy
+ *
+ * \return Pointer to copied value (from source)
+ */
+static inline const char *
+crm_copy_xml_element(const xmlNode *obj1, xmlNode *obj2, const char *element)
+{
+ const char *value = crm_element_value(obj1, element);
+
+ crm_xml_add(obj2, element, value);
+ return value;
+}
+
+# ifdef __cplusplus
+}
+# endif
+
+#endif // PCMK__CRM_COMMON_NVPAIR__H
diff --git a/include/crm/common/options_internal.h b/include/crm/common/options_internal.h
new file mode 100644
index 0000000..4157b58
--- /dev/null
+++ b/include/crm/common/options_internal.h
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2006-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 PCMK__OPTIONS_INTERNAL__H
+# define PCMK__OPTIONS_INTERNAL__H
+
+# ifndef PCMK__CONFIG_H
+# define PCMK__CONFIG_H
+# include <config.h> // _Noreturn
+# endif
+
+# include <glib.h> // GHashTable
+# include <stdbool.h> // bool
+
+_Noreturn void pcmk__cli_help(char cmd);
+
+
+/*
+ * Environment variable option handling
+ */
+
+const char *pcmk__env_option(const char *option);
+void pcmk__set_env_option(const char *option, const char *value);
+bool pcmk__env_option_enabled(const char *daemon, const char *option);
+
+
+/*
+ * Cluster option handling
+ */
+
+typedef struct pcmk__cluster_option_s {
+ const char *name;
+ const char *alt_name;
+ const char *type;
+ const char *values;
+ const char *default_value;
+
+ bool (*is_valid)(const char *);
+
+ const char *description_short;
+ const char *description_long;
+
+} pcmk__cluster_option_t;
+
+const char *pcmk__cluster_option(GHashTable *options,
+ const pcmk__cluster_option_t *option_list,
+ int len, const char *name);
+
+gchar *pcmk__format_option_metadata(const char *name, const char *desc_short,
+ const char *desc_long,
+ pcmk__cluster_option_t *option_list,
+ int len);
+
+void pcmk__validate_cluster_options(GHashTable *options,
+ pcmk__cluster_option_t *option_list,
+ int len);
+
+bool pcmk__valid_interval_spec(const char *value);
+bool pcmk__valid_boolean(const char *value);
+bool pcmk__valid_number(const char *value);
+bool pcmk__valid_positive_number(const char *value);
+bool pcmk__valid_quorum(const char *value);
+bool pcmk__valid_script(const char *value);
+bool pcmk__valid_percentage(const char *value);
+
+// from watchdog.c
+long pcmk__get_sbd_timeout(void);
+bool pcmk__get_sbd_sync_resource_startup(void);
+long pcmk__auto_watchdog_timeout(void);
+bool pcmk__valid_sbd_timeout(const char *value);
+
+// Constants for environment variable names
+#define PCMK__ENV_BLACKBOX "blackbox"
+#define PCMK__ENV_CLUSTER_TYPE "cluster_type"
+#define PCMK__ENV_DEBUG "debug"
+#define PCMK__ENV_LOGFACILITY "logfacility"
+#define PCMK__ENV_LOGFILE "logfile"
+#define PCMK__ENV_LOGPRIORITY "logpriority"
+#define PCMK__ENV_MCP "mcp"
+#define PCMK__ENV_NODE_START_STATE "node_start_state"
+#define PCMK__ENV_PHYSICAL_HOST "physical_host"
+#define PCMK__ENV_QUORUM_TYPE "quorum_type"
+#define PCMK__ENV_SHUTDOWN_DELAY "shutdown_delay"
+#define PCMK__ENV_STDERR "stderr"
+
+// Constants for cluster option names
+#define PCMK__OPT_NODE_HEALTH_BASE "node-health-base"
+#define PCMK__OPT_NODE_HEALTH_GREEN "node-health-green"
+#define PCMK__OPT_NODE_HEALTH_RED "node-health-red"
+#define PCMK__OPT_NODE_HEALTH_STRATEGY "node-health-strategy"
+#define PCMK__OPT_NODE_HEALTH_YELLOW "node-health-yellow"
+
+// Constants for meta-attribute names
+#define PCMK__META_ALLOW_UNHEALTHY_NODES "allow-unhealthy-nodes"
+
+// Constants for enumerated values for various options
+#define PCMK__VALUE_CLUSTER "cluster"
+#define PCMK__VALUE_CUSTOM "custom"
+#define PCMK__VALUE_FENCING "fencing"
+#define PCMK__VALUE_GREEN "green"
+#define PCMK__VALUE_LOCAL "local"
+#define PCMK__VALUE_MIGRATE_ON_RED "migrate-on-red"
+#define PCMK__VALUE_NONE "none"
+#define PCMK__VALUE_NOTHING "nothing"
+#define PCMK__VALUE_ONLY_GREEN "only-green"
+#define PCMK__VALUE_PROGRESSIVE "progressive"
+#define PCMK__VALUE_QUORUM "quorum"
+#define PCMK__VALUE_RED "red"
+#define PCMK__VALUE_UNFENCING "unfencing"
+#define PCMK__VALUE_YELLOW "yellow"
+
+#endif // PCMK__OPTIONS_INTERNAL__H
diff --git a/include/crm/common/output.h b/include/crm/common/output.h
new file mode 100644
index 0000000..112ebcb
--- /dev/null
+++ b/include/crm/common/output.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2021-2023 the Pacemaker project contributors
+ *
+ * The version control history for this file may have further details.
+ *
+ * This source code is licensed under the GNU Lesser General Public License
+ * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
+ */
+
+#ifndef PCMK__CRM_COMMON_OUTPUT__H
+# define PCMK__CRM_COMMON_OUTPUT__H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * \file
+ * \brief Control output from tools
+ * \ingroup core
+ */
+
+/*!
+ * \brief Control which sections are output
+ */
+typedef enum {
+ pcmk_section_stack = 1 << 0,
+ pcmk_section_dc = 1 << 1,
+ pcmk_section_times = 1 << 2,
+ pcmk_section_counts = 1 << 3,
+ pcmk_section_options = 1 << 4,
+ pcmk_section_nodes = 1 << 5,
+ pcmk_section_resources = 1 << 6,
+ pcmk_section_attributes = 1 << 7,
+ pcmk_section_failcounts = 1 << 8,
+ pcmk_section_operations = 1 << 9,
+ pcmk_section_fence_failed = 1 << 10,
+ pcmk_section_fence_pending = 1 << 11,
+ pcmk_section_fence_worked = 1 << 12,
+ pcmk_section_tickets = 1 << 13,
+ pcmk_section_bans = 1 << 14,
+ pcmk_section_failures = 1 << 15,
+ pcmk_section_maint_mode = 1 << 16,
+} pcmk_section_e;
+
+#define pcmk_section_fencing_all (pcmk_section_fence_failed | pcmk_section_fence_pending | pcmk_section_fence_worked)
+#define pcmk_section_summary (pcmk_section_stack | pcmk_section_dc | pcmk_section_times | \
+ pcmk_section_counts | pcmk_section_maint_mode)
+#define pcmk_section_all (pcmk_section_summary | pcmk_section_options | pcmk_section_nodes | \
+ pcmk_section_resources | pcmk_section_attributes | pcmk_section_failcounts | \
+ pcmk_section_operations | pcmk_section_fencing_all | pcmk_section_tickets | \
+ pcmk_section_bans | pcmk_section_failures | pcmk_section_maint_mode)
+
+/*!
+ * \brief Further modify the output of sections
+ */
+typedef enum {
+ pcmk_show_brief = 1 << 0,
+ pcmk_show_clone_detail = 1 << 1,
+ pcmk_show_node_id = 1 << 2,
+ pcmk_show_implicit_rscs = 1 << 3,
+ pcmk_show_timing = 1 << 4,
+ pcmk_show_inactive_rscs = 1 << 5,
+ pcmk_show_rscs_by_node = 1 << 6,
+ pcmk_show_pending = 1 << 7,
+ pcmk_show_rsc_only = 1 << 8,
+ pcmk_show_failed_detail = 1 << 9,
+ pcmk_show_feature_set = 1 << 10,
+ pcmk_show_description = 1 << 11,
+} pcmk_show_opt_e;
+
+#define pcmk_show_details ((pcmk_show_clone_detail) \
+ | (pcmk_show_node_id) \
+ | (pcmk_show_implicit_rscs) \
+ | (pcmk_show_failed_detail) \
+ | (pcmk_show_feature_set) \
+ | (pcmk_show_description))
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // PCMK__CRM_COMMON_OUTPUT__H
diff --git a/include/crm/common/output_internal.h b/include/crm/common/output_internal.h
new file mode 100644
index 0000000..e7b631e
--- /dev/null
+++ b/include/crm/common/output_internal.h
@@ -0,0 +1,989 @@
+/*
+ * Copyright 2019-2023 the Pacemaker project contributors
+ *
+ * The version control history for this file may have further details.
+ *
+ * This source code is licensed under the GNU Lesser General Public License
+ * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
+ */
+
+#ifndef PCMK__OUTPUT_INTERNAL__H
+# define PCMK__OUTPUT_INTERNAL__H
+
+# include <stdbool.h>
+# include <stdint.h>
+# include <stdio.h>
+# include <libxml/tree.h>
+# include <libxml/HTMLtree.h>
+
+# include <glib.h>
+# include <crm/common/results.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * \file
+ * \brief Formatted output for pacemaker tools
+ */
+
+#if defined(PCMK__WITH_ATTRIBUTE_OUTPUT_ARGS)
+# define PCMK__OUTPUT_ARGS(ARGS...) __attribute__((output_args(ARGS)))
+#else
+# define PCMK__OUTPUT_ARGS(ARGS...)
+#endif
+
+typedef struct pcmk__output_s pcmk__output_t;
+
+/*!
+ * \internal
+ * \brief The type of a function that creates a ::pcmk__output_t.
+ *
+ * Instances of this type are passed to pcmk__register_format(), stored in an
+ * internal data structure, and later accessed by pcmk__output_new(). For
+ * examples, see pcmk__mk_xml_output() and pcmk__mk_text_output().
+ *
+ * \param[in] argv The list of command line arguments.
+ */
+typedef pcmk__output_t * (*pcmk__output_factory_t)(char **argv);
+
+/*!
+ * \internal
+ * \brief The type of a custom message formatting function.
+ *
+ * These functions are defined by various libraries to support formatting of
+ * types aside from the basic types provided by a ::pcmk__output_t.
+ *
+ * The meaning of the return value will be different for each message.
+ * In general, however, 0 should be returned on success and a positive value
+ * on error.
+ *
+ * \param[in,out] out Output object to use to display message
+ * \param[in,out] args Message-specific arguments needed
+ *
+ * \note These functions must not call va_start or va_end - that is done
+ * automatically before the custom formatting function is called.
+ */
+typedef int (*pcmk__message_fn_t)(pcmk__output_t *out, va_list args);
+
+/*!
+ * \internal
+ * \brief Internal type for tracking custom messages.
+ *
+ * Each library can register functions that format custom message types. These
+ * are commonly used to handle some library-specific type. Registration is
+ * done by first defining a table of ::pcmk__message_entry_t structures and
+ * then passing that table to pcmk__register_messages(). Separate handlers
+ * can be defined for the same message, but for different formats (xml vs.
+ * text). Unknown formats will be ignored.
+ *
+ * Additionally, a "default" value for fmt_table can be used. In this case,
+ * fn will be registered for all supported formats. It is also possible to
+ * register a default and then override that registration with a format-specific
+ * function if necessary.
+ *
+ * \note The ::pcmk__message_entry_t table is processed in one pass, in order,
+ * from top to bottom. This means later entries with the same message_id will
+ * override previous ones. Thus, any default entry must come before any
+ * format-specific entries for the same message_id.
+ */
+typedef struct pcmk__message_entry_s {
+ /*!
+ * \brief The message to be handled.
+ *
+ * This must be the same ID that is passed to the message function of
+ * a ::pcmk__output_t. Unknown message IDs will be ignored.
+ */
+ const char *message_id;
+
+ /*!
+ * \brief The format type this handler is for.
+ *
+ * This name must match the fmt_name of the currently active formatter in
+ * order for the registered function to be called. It is valid to have
+ * multiple entries for the same message_id but with different fmt_name
+ * values.
+ */
+ const char *fmt_name;
+
+ /*!
+ * \brief The function to be called for message_id given a match on
+ * fmt_name. See comments on ::pcmk__message_fn_t.
+ */
+ pcmk__message_fn_t fn;
+} pcmk__message_entry_t;
+
+/*!
+ * \internal
+ * \brief This structure contains everything needed to add support for a
+ * single output formatter to a command line program.
+ */
+typedef struct pcmk__supported_format_s {
+ /*!
+ * \brief The name of this output formatter, which should match the
+ * fmt_name parameter in some ::pcmk__output_t structure.
+ */
+ const char *name;
+
+ /*!
+ * \brief A function that creates a ::pcmk__output_t.
+ */
+ pcmk__output_factory_t create;
+
+ /*!
+ * \brief Format-specific command line options. This can be NULL if
+ * no command line options should be supported.
+ */
+ GOptionEntry *options;
+} pcmk__supported_format_t;
+
+/* The following three blocks need to be updated each time a new base formatter
+ * is added.
+ */
+
+extern GOptionEntry pcmk__html_output_entries[];
+extern GOptionEntry pcmk__log_output_entries[];
+extern GOptionEntry pcmk__none_output_entries[];
+extern GOptionEntry pcmk__text_output_entries[];
+extern GOptionEntry pcmk__xml_output_entries[];
+
+pcmk__output_t *pcmk__mk_html_output(char **argv);
+pcmk__output_t *pcmk__mk_log_output(char **argv);
+pcmk__output_t *pcmk__mk_none_output(char **argv);
+pcmk__output_t *pcmk__mk_text_output(char **argv);
+pcmk__output_t *pcmk__mk_xml_output(char **argv);
+
+#define PCMK__SUPPORTED_FORMAT_HTML { "html", pcmk__mk_html_output, pcmk__html_output_entries }
+#define PCMK__SUPPORTED_FORMAT_LOG { "log", pcmk__mk_log_output, pcmk__log_output_entries }
+#define PCMK__SUPPORTED_FORMAT_NONE { PCMK__VALUE_NONE, pcmk__mk_none_output, \
+ pcmk__none_output_entries }
+#define PCMK__SUPPORTED_FORMAT_TEXT { "text", pcmk__mk_text_output, pcmk__text_output_entries }
+#define PCMK__SUPPORTED_FORMAT_XML { "xml", pcmk__mk_xml_output, pcmk__xml_output_entries }
+
+/*!
+ * \brief This structure contains everything that makes up a single output
+ * formatter.
+ *
+ * Instances of this structure may be created by calling pcmk__output_new()
+ * with the name of the desired formatter. They should later be freed with
+ * pcmk__output_free().
+ */
+struct pcmk__output_s {
+ /*!
+ * \brief The name of this output formatter.
+ */
+ const char *fmt_name;
+
+ /*!
+ * \brief Should this formatter supress most output?
+ *
+ * \note This setting is not respected by all formatters. In general,
+ * machine-readable output formats will not support this while
+ * user-oriented formats will. Callers should use is_quiet()
+ * to test whether to print or not.
+ */
+ bool quiet;
+
+ /*!
+ * \brief A copy of the request that generated this output.
+ *
+ * In the case of command line usage, this would be the command line
+ * arguments. For other use cases, it could be different.
+ */
+ gchar *request;
+
+ /*!
+ * \brief Where output should be written.
+ *
+ * This could be a file handle, or stdout or stderr. This is really only
+ * useful internally.
+ */
+ FILE *dest;
+
+ /*!
+ * \brief Custom messages that are currently registered on this formatter.
+ *
+ * Keys are the string message IDs, values are ::pcmk__message_fn_t function
+ * pointers.
+ */
+ GHashTable *messages;
+
+ /*!
+ * \brief Implementation-specific private data.
+ *
+ * Each individual formatter may have some private data useful in its
+ * implementation. This points to that data. Callers should not rely on
+ * its contents or structure.
+ */
+ void *priv;
+
+ /*!
+ * \internal
+ * \brief Take whatever actions are necessary to prepare out for use. This is
+ * called by pcmk__output_new(). End users should not need to call this.
+ *
+ * \note For formatted output implementers - This function should be written in
+ * such a way that it can be called repeatedly on an already initialized
+ * object without causing problems, or on a previously finished object
+ * without crashing.
+ *
+ * \param[in,out] out The output functions structure.
+ *
+ * \return true on success, false on error.
+ */
+ bool (*init) (pcmk__output_t *out);
+
+ /*!
+ * \internal
+ * \brief Free the private formatter-specific data.
+ *
+ * This is called from pcmk__output_free() and does not typically need to be
+ * called directly.
+ *
+ * \param[in,out] out The output functions structure.
+ */
+ void (*free_priv) (pcmk__output_t *out);
+
+ /*!
+ * \internal
+ * \brief Take whatever actions are necessary to end formatted output.
+ *
+ * This could include flushing output to a file, but does not include freeing
+ * anything. The finish method can potentially be fairly complicated, adding
+ * additional information to the internal data structures or doing whatever
+ * else. It is therefore suggested that finish only be called once.
+ *
+ * \note The print parameter will only affect those formatters that do all
+ * their output at the end. Console-oriented formatters typically print
+ * a line at a time as they go, so this parameter will not affect them.
+ * Structured formatters will honor it, however.
+ *
+ * \note The copy_dest parameter does not apply to all formatters. Console-
+ * oriented formatters do not build up a structure as they go, and thus
+ * do not have anything to return. Structured formatters will honor it,
+ * however. Note that each type of formatter will return a different
+ * type of value in this parameter. To use this parameter, call this
+ * function like so:
+ *
+ * \code
+ * xmlNode *dest = NULL;
+ * out->finish(out, exit_code, false, (void **) &dest);
+ * \endcode
+ *
+ * \param[in,out] out The output functions structure.
+ * \param[in] exit_status The exit value of the whole program.
+ * \param[in] print Whether this function should write any output.
+ * \param[out] copy_dest A destination to store a copy of the internal
+ * data structure for this output, or NULL if no
+ * copy is required. The caller should free this
+ * memory when done with it.
+ */
+ void (*finish) (pcmk__output_t *out, crm_exit_t exit_status, bool print,
+ void **copy_dest);
+
+ /*!
+ * \internal
+ * \brief Finalize output and then immediately set back up to start a new set
+ * of output.
+ *
+ * This is conceptually the same as calling finish and then init, though in
+ * practice more be happening behind the scenes.
+ *
+ * \note This function differs from finish in that no exit_status is added.
+ * The idea is that the program is not shutting down, so there is not
+ * yet a final exit code. Call finish on the last time through if this
+ * is needed.
+ *
+ * \param[in,out] out The output functions structure.
+ */
+ void (*reset) (pcmk__output_t *out);
+
+ /*!
+ * \internal
+ * \brief Register a custom message.
+ *
+ * \param[in,out] out The output functions structure.
+ * \param[in] message_id The name of the message to register. This name
+ * will be used as the message_id parameter to the
+ * message function in order to call the custom
+ * format function.
+ * \param[in] fn The custom format function to call for message_id.
+ */
+ void (*register_message) (pcmk__output_t *out, const char *message_id,
+ pcmk__message_fn_t fn);
+
+ /*!
+ * \internal
+ * \brief Call a previously registered custom message.
+ *
+ * \param[in,out] out The output functions structure.
+ * \param[in] message_id The name of the message to call. This name must
+ * be the same as the message_id parameter of some
+ * previous call to register_message.
+ * \param[in] ... Arguments to be passed to the registered function.
+ *
+ * \return A standard Pacemaker return code. Generally: 0 if a function was
+ * registered for the message, that function was called, and returned
+ * successfully; EINVAL if no function was registered; or pcmk_rc_no_output
+ * if a function was called but produced no output.
+ */
+ int (*message) (pcmk__output_t *out, const char *message_id, ...);
+
+ /*!
+ * \internal
+ * \brief Format the output of a completed subprocess.
+ *
+ * \param[in,out] out The output functions structure.
+ * \param[in] exit_status The exit value of the subprocess.
+ * \param[in] proc_stdout stdout from the completed subprocess.
+ * \param[in] proc_stderr stderr from the completed subprocess.
+ */
+ void (*subprocess_output) (pcmk__output_t *out, int exit_status,
+ const char *proc_stdout, const char *proc_stderr);
+
+ /*!
+ * \internal
+ * \brief Format version information. This is useful for the --version
+ * argument of command line tools.
+ *
+ * \param[in,out] out The output functions structure.
+ * \param[in] extended Add additional version information.
+ */
+ void (*version) (pcmk__output_t *out, bool extended);
+
+ /*!
+ * \internal
+ * \brief Format an informational message that should be shown to
+ * to an interactive user. Not all formatters will do this.
+ *
+ * \note A newline will automatically be added to the end of the format
+ * string, so callers should not include a newline.
+ *
+ * \note It is possible for a formatter that supports this method to
+ * still not print anything out if is_quiet returns true.
+ *
+ * \param[in,out] out The output functions structure.
+ * \param[in] buf The message to be printed.
+ * \param[in] ... Arguments to be formatted.
+ *
+ * \return A standard Pacemaker return code. Generally: pcmk_rc_ok
+ * if output was produced and pcmk_rc_no_output if it was not.
+ * As not all formatters implement this function, those that
+ * do not will always just return pcmk_rc_no_output.
+ */
+ int (*info) (pcmk__output_t *out, const char *format, ...) G_GNUC_PRINTF(2, 3);
+
+ /*!
+ * \internal
+ * \brief Like \p info() but for messages that should appear only
+ * transiently. Not all formatters will do this.
+ *
+ * The originally envisioned use case is for console output, where a
+ * transient status-related message may be quickly overwritten by a refresh.
+ *
+ * \param[in,out] out The output functions structure.
+ * \param[in] format The format string of the message to be printed.
+ * \param[in] ... Arguments to be formatted.
+ *
+ * \return A standard Pacemaker return code. Generally: \p pcmk_rc_ok if
+ * output was produced and \p pcmk_rc_no_output if it was not. As
+ * not all formatters implement this function, those that do not
+ * will always just return \p pcmk_rc_no_output.
+ */
+ int (*transient) (pcmk__output_t *out, const char *format, ...)
+ G_GNUC_PRINTF(2, 3);
+
+ /*!
+ * \internal
+ * \brief Format an error message that should be shown to an interactive
+ * user. Not all formatters will do this.
+ *
+ * \note A newline will automatically be added to the end of the format
+ * string, so callers should not include a newline.
+ *
+ * \note Formatters that support this method should always generate output,
+ * even if is_quiet returns true.
+ *
+ * \param[in,out] out The output functions structure.
+ * \param[in] buf The message to be printed.
+ * \param[in] ... Arguments to be formatted.
+ */
+ void (*err) (pcmk__output_t *out, const char *format, ...) G_GNUC_PRINTF(2, 3);
+
+ /*!
+ * \internal
+ * \brief Format already formatted XML.
+ *
+ * \param[in,out] out The output functions structure.
+ * \param[in] name A name to associate with the XML.
+ * \param[in] buf The XML in a string.
+ */
+ void (*output_xml) (pcmk__output_t *out, const char *name, const char *buf);
+
+ /*!
+ * \internal
+ * \brief Start a new list of items.
+ *
+ * \note For text output, this corresponds to another level of indentation. For
+ * XML output, this corresponds to wrapping any following output in another
+ * layer of tags.
+ *
+ * \note If singular_noun and plural_noun are non-NULL, calling end_list will
+ * result in a summary being added.
+ *
+ * \param[in,out] out The output functions structure.
+ * \param[in] singular_noun When outputting the summary for a list with
+ * one item, the noun to use.
+ * \param[in] plural_noun When outputting the summary for a list with
+ * more than one item, the noun to use.
+ * \param[in] format The format string.
+ * \param[in] ... Arguments to be formatted.
+ */
+ void (*begin_list) (pcmk__output_t *out, const char *singular_noun,
+ const char *plural_noun, const char *format, ...)
+ G_GNUC_PRINTF(4, 5);
+
+ /*!
+ * \internal
+ * \brief Format a single item in a list.
+ *
+ * \param[in,out] out The output functions structure.
+ * \param[in] name A name to associate with this item.
+ * \param[in] format The format string.
+ * \param[in] ... Arguments to be formatted.
+ */
+ void (*list_item) (pcmk__output_t *out, const char *name, const char *format, ...)
+ G_GNUC_PRINTF(3, 4);
+
+ /*!
+ * \internal
+ * \brief Increment the internal counter of the current list's length.
+ *
+ * Typically, this counter is maintained behind the scenes as a side effect
+ * of calling list_item(). However, custom functions that maintain lists
+ * some other way will need to manage this counter manually. This is
+ * useful for implementing custom message functions and should not be
+ * needed otherwise.
+ *
+ * \param[in,out] out The output functions structure.
+ */
+ void (*increment_list) (pcmk__output_t *out);
+
+ /*!
+ * \internal
+ * \brief Conclude a list.
+ *
+ * \note If begin_list was called with non-NULL for both the singular_noun
+ * and plural_noun arguments, this function will output a summary.
+ * Otherwise, no summary will be added.
+ *
+ * \param[in,out] out The output functions structure.
+ */
+ void (*end_list) (pcmk__output_t *out);
+
+ /*!
+ * \internal
+ * \brief Should anything be printed to the user?
+ *
+ * \note This takes into account both the \p quiet value as well as the
+ * current formatter.
+ *
+ * \param[in,out] out The output functions structure.
+ *
+ * \return true if output should be supressed, false otherwise.
+ */
+ bool (*is_quiet) (pcmk__output_t *out);
+
+ /*!
+ * \internal
+ * \brief Output a spacer. Not all formatters will do this.
+ *
+ * \param[in,out] out The output functions structure.
+ */
+ void (*spacer) (pcmk__output_t *out);
+
+ /*!
+ * \internal
+ * \brief Output a progress indicator. This is likely only useful for
+ * plain text, console based formatters.
+ *
+ * \param[in,out] out The output functions structure
+ * \param[in] end If true, output a newline afterwards (this should
+ * only be used the last time this function is called)
+ *
+ */
+ void (*progress) (pcmk__output_t *out, bool end);
+
+ /*!
+ * \internal
+ * \brief Prompt the user for input. Not all formatters will do this.
+ *
+ * \note This function is part of pcmk__output_t, but unlike all other
+ * function it does not take that as an argument. In general, a
+ * prompt will go directly to the screen and therefore bypass any
+ * need to use the formatted output code to decide where and how
+ * to display.
+ *
+ * \param[in] prompt The prompt to display. This is required.
+ * \param[in] echo If true, echo the user's input to the screen. Set
+ * to false for password entry.
+ * \param[out] dest Where to store the user's response. This is
+ * required.
+ */
+ void (*prompt) (const char *prompt, bool echo, char **dest);
+};
+
+/*!
+ * \internal
+ * \brief Call a formatting function for a previously registered message.
+ *
+ * \note This function is for implementing custom formatters. It should not
+ * be called directly. Instead, call out->message.
+ *
+ * \param[in,out] out The output functions structure.
+ * \param[in] message_id The message to be handled. Unknown messages
+ * will be ignored.
+ * \param[in] ... Arguments to be passed to the registered function.
+ */
+int
+pcmk__call_message(pcmk__output_t *out, const char *message_id, ...);
+
+/*!
+ * \internal
+ * \brief Free a ::pcmk__output_t structure that was previously created by
+ * pcmk__output_new().
+ *
+ * \note While the create and finish functions are designed in such a way that
+ * they can be called repeatedly, this function will completely free the
+ * memory of the object. Once this function has been called, producing
+ * more output requires starting over from pcmk__output_new().
+ *
+ * \param[in,out] out The output structure.
+ */
+void pcmk__output_free(pcmk__output_t *out);
+
+/*!
+ * \internal
+ * \brief Create a new ::pcmk__output_t structure.
+ *
+ * This also registers message functions from libcrmcommon.
+ *
+ * \param[in,out] out The destination of the new ::pcmk__output_t.
+ * \param[in] fmt_name How should output be formatted?
+ * \param[in] filename Where should formatted output be written to? This
+ * can be a filename (which will be overwritten if it
+ * already exists), or NULL or "-" for stdout. For no
+ * output, pass a filename of "/dev/null".
+ * \param[in] argv The list of command line arguments.
+ *
+ * \return Standard Pacemaker return code
+ */
+int pcmk__output_new(pcmk__output_t **out, const char *fmt_name,
+ const char *filename, char **argv);
+
+/*!
+ * \internal
+ * \brief Register a new output formatter, making it available for use
+ * the same as a base formatter.
+ *
+ * \param[in,out] group A ::GOptionGroup that formatted output related command
+ * line arguments should be added to. This can be NULL
+ * for use outside of command line programs.
+ * \param[in] name The name of the format. This will be used to select a
+ * format from command line options and for displaying help.
+ * \param[in] create A function that creates a ::pcmk__output_t.
+ * \param[in] options Format-specific command line options. These will be
+ * added to the context. This argument can also be NULL.
+ *
+ * \return Standard Pacemaker return code
+ */
+int
+pcmk__register_format(GOptionGroup *group, const char *name,
+ pcmk__output_factory_t create,
+ const GOptionEntry *options);
+
+/*!
+ * \internal
+ * \brief Register an entire table of output formatters at once.
+ *
+ * \param[in,out] group A ::GOptionGroup that formatted output related command
+ * line arguments should be added to. This can be NULL
+ * for use outside of command line programs.
+ * \param[in] table An array of ::pcmk__supported_format_t which should
+ * all be registered. This array must be NULL-terminated.
+ *
+ */
+void
+pcmk__register_formats(GOptionGroup *group,
+ const pcmk__supported_format_t *table);
+
+/*!
+ * \internal
+ * \brief Unregister a previously registered table of custom formatting
+ * functions and destroy the internal data structures associated with them.
+ */
+void
+pcmk__unregister_formats(void);
+
+/*!
+ * \internal
+ * \brief Register a function to handle a custom message.
+ *
+ * \note This function is for implementing custom formatters. It should not
+ * be called directly. Instead, call out->register_message.
+ *
+ * \param[in,out] out The output functions structure.
+ * \param[in] message_id The message to be handled.
+ * \param[in] fn The custom format function to call for message_id.
+ */
+void
+pcmk__register_message(pcmk__output_t *out, const char *message_id,
+ pcmk__message_fn_t fn);
+
+/*!
+ * \internal
+ * \brief Register an entire table of custom formatting functions at once.
+ *
+ * This table can contain multiple formatting functions for the same message ID
+ * if they are for different format types.
+ *
+ * \param[in,out] out The output functions structure.
+ * \param[in] table An array of ::pcmk__message_entry_t values which should
+ * all be registered. This array must be NULL-terminated.
+ */
+void
+pcmk__register_messages(pcmk__output_t *out,
+ const pcmk__message_entry_t *table);
+
+/* Functions that are useful for implementing custom message formatters */
+
+/*!
+ * \internal
+ * \brief A printf-like function.
+ *
+ * This function writes to out->dest and indents the text to the current level
+ * of the text formatter's nesting. This function should be used when implementing
+ * custom message functions for the text output format. It should not be used
+ * for any other purpose.
+ *
+ * Typically, this function should be used instead of printf.
+ *
+ * \param[in,out] out The output functions structure.
+ * \param[in] format The format string.
+ * \param[in] ... Arguments to be passed to the format string.
+ */
+void
+pcmk__indented_printf(pcmk__output_t *out, const char *format, ...) G_GNUC_PRINTF(2, 3);
+
+/*!
+ * \internal
+ * \brief A vprintf-like function.
+ *
+ * This function is like pcmk__indented_printf(), except it takes a va_list instead
+ * of a list of arguments. This function should be used when implementing custom
+ * functions for the text output format. It should not be used for any other purpose.
+ *
+ * Typically, this function should be used instead of vprintf.
+ *
+ * \param[in,out] out The output functions structure.
+ * \param[in] format The format string.
+ * \param[in] args A list of arguments to apply to the format string.
+ */
+void
+pcmk__indented_vprintf(pcmk__output_t *out, const char *format, va_list args) G_GNUC_PRINTF(2, 0);
+
+
+/*!
+ * \internal
+ * \brief A printf-like function.
+ *
+ * This function writes to out->dest without indenting the text. This function
+ * should be used when implementing custom message functions for the text output
+ * format. It should not be used for any other purpose.
+ *
+ * \param[in,out] out The output functions structure.
+ * \param[in] format The format string.
+ * \param[in] ... Arguments to be passed to the format string.
+ */
+void
+pcmk__formatted_printf(pcmk__output_t *out, const char *format, ...) G_GNUC_PRINTF(2, 3);
+
+/*!
+ * \internal
+ * \brief A vprintf-like function.
+ *
+ * This function is like pcmk__formatted_printf(), except it takes a va_list instead
+ * of a list of arguments. This function should be used when implementing custom
+ * message functions for the text output format. It should not be used for any
+ * other purpose.
+ *
+ * \param[in,out] out The output functions structure.
+ * \param[in] format The format string.
+ * \param[in] args A list of arguments to apply to the format string.
+ */
+void
+pcmk__formatted_vprintf(pcmk__output_t *out, const char *format, va_list args) G_GNUC_PRINTF(2, 0);
+
+/*!
+ * \internal
+ * \brief Prompt the user for input.
+ *
+ * \param[in] prompt The prompt to display
+ * \param[in] echo If true, echo the user's input to the screen. Set
+ * to false for password entry.
+ * \param[out] dest Where to store the user's response.
+ */
+void
+pcmk__text_prompt(const char *prompt, bool echo, char **dest);
+
+/*!
+ * \internal
+ * \brief Get the log level used by the formatted output logger
+ *
+ * \param[in] out Output object
+ *
+ * \return Log level used by \p out
+ */
+uint8_t
+pcmk__output_get_log_level(const pcmk__output_t *out);
+
+/*!
+ * \internal
+ * \brief Set the log level used by the formatted output logger.
+ *
+ * \param[in,out] out The output functions structure.
+ * \param[in] log_level The log level constant (LOG_INFO, LOG_ERR, etc.)
+ * to use.
+ *
+ * \note By default, LOG_INFO is used.
+ * \note Almost all formatted output messages will respect this setting.
+ * However, out->err will always log at LOG_ERR.
+ */
+void
+pcmk__output_set_log_level(pcmk__output_t *out, uint8_t log_level);
+
+/*!
+ * \internal
+ * \brief Create and return a new XML node with the given name, as a child of the
+ * current list parent. The new node is then added as the new list parent,
+ * meaning all subsequent nodes will be its children. This is used when
+ * implementing custom functions.
+ *
+ * \param[in,out] out The output functions structure.
+ * \param[in] name The name of the node to be created.
+ * \param[in] ... Name/value pairs to set as XML properties.
+ */
+xmlNodePtr
+pcmk__output_xml_create_parent(pcmk__output_t *out, const char *name, ...)
+G_GNUC_NULL_TERMINATED;
+
+/*!
+ * \internal
+ * \brief Add a copy of the given node as a child of the current list parent.
+ * This is used when implementing custom message functions.
+ *
+ * \param[in,out] out The output functions structure.
+ * \param[in] node An XML node to copy as a child.
+ */
+void
+pcmk__output_xml_add_node_copy(pcmk__output_t *out, xmlNodePtr node);
+
+/*!
+ * \internal
+ * \brief Create and return a new XML node with the given name, as a child of the
+ * current list parent. This is used when implementing custom functions.
+ *
+ * \param[in,out] out The output functions structure.
+ * \param[in] name The name of the node to be created.
+ * \param[in] ... Name/value pairs to set as XML properties.
+ */
+xmlNodePtr
+pcmk__output_create_xml_node(pcmk__output_t *out, const char *name, ...)
+G_GNUC_NULL_TERMINATED;
+
+/*!
+ * \internal
+ * \brief Like pcmk__output_create_xml_node(), but add the given text content to the
+ * new node.
+ *
+ * \param[in,out] out The output functions structure.
+ * \param[in] name The name of the node to be created.
+ * \param[in] content The text content of the node.
+ */
+xmlNodePtr
+pcmk__output_create_xml_text_node(pcmk__output_t *out, const char *name, const char *content);
+
+/*!
+ * \internal
+ * \brief Push a parent XML node onto the stack. This is used when implementing
+ * custom message functions.
+ *
+ * The XML output formatter maintains an internal stack to keep track of which nodes
+ * are parents in order to build up the tree structure. This function can be used
+ * to temporarily push a new node onto the stack. After calling this function, any
+ * other formatting functions will have their nodes added as children of this new
+ * parent.
+ *
+ * \param[in,out] out The output functions structure
+ * \param[in] parent XML node to add
+ */
+void
+pcmk__output_xml_push_parent(pcmk__output_t *out, xmlNodePtr parent);
+
+/*!
+ * \internal
+ * \brief Pop a parent XML node onto the stack. This is used when implementing
+ * custom message functions.
+ *
+ * This function removes a parent node from the stack. See pcmk__xml_push_parent()
+ * for more details.
+ *
+ * \note Little checking is done with this function. Be sure you only pop parents
+ * that were previously pushed. In general, it is best to keep the code between
+ * push and pop simple.
+ *
+ * \param[in,out] out The output functions structure.
+ */
+void
+pcmk__output_xml_pop_parent(pcmk__output_t *out);
+
+/*!
+ * \internal
+ * \brief Peek a parent XML node onto the stack. This is used when implementing
+ * custom message functions.
+ *
+ * This function peeks a parent node on stack. See pcmk__xml_push_parent()
+ * for more details. It has no side-effect and can be called for an empty stack.
+ *
+ * \note Little checking is done with this function.
+ *
+ * \param[in,out] out The output functions structure.
+ *
+ * \return NULL if stack is empty, otherwise the parent of the stack.
+ */
+xmlNodePtr
+pcmk__output_xml_peek_parent(pcmk__output_t *out);
+
+/*!
+ * \internal
+ * \brief Create a new XML node consisting of the provided text inside an HTML
+ * element node of the given name.
+ *
+ * \param[in,out] out The output functions structure.
+ * \param[in] element_name The name of the new HTML element.
+ * \param[in] id The CSS ID selector to apply to this element.
+ * If NULL, no ID is added.
+ * \param[in] class_name The CSS class selector to apply to this element.
+ * If NULL, no class is added.
+ * \param[in] text The text content of the node.
+ */
+xmlNodePtr
+pcmk__output_create_html_node(pcmk__output_t *out, const char *element_name, const char *id,
+ const char *class_name, const char *text);
+
+/*!
+ * \internal
+ * \brief Add an HTML tag to the <head> section.
+ *
+ * The arguments after name are a NULL-terminated list of keys and values,
+ * all of which will be added as attributes to the given tag. For instance,
+ * the following code would generate the tag "<meta http-equiv='refresh' content='19'>":
+ *
+ * \code
+ * pcmk__html_add_header("meta", "http-equiv", "refresh", "content", "19", NULL);
+ * \endcode
+ *
+ * \param[in] name The HTML tag for the new node.
+ * \param[in] ... A NULL-terminated key/value list of attributes.
+ */
+void
+pcmk__html_add_header(const char *name, ...)
+G_GNUC_NULL_TERMINATED;
+
+/*!
+ * \internal
+ * \brief Handle end-of-program error reporting
+ *
+ * \param[in,out] error A GError object potentially containing some error.
+ * If NULL, do nothing.
+ * \param[in,out] out The output functions structure. If NULL, any errors
+ * will simply be printed to stderr.
+ */
+void pcmk__output_and_clear_error(GError **error, pcmk__output_t *out);
+
+int pcmk__xml_output_new(pcmk__output_t **out, xmlNodePtr *xml);
+void pcmk__xml_output_finish(pcmk__output_t *out, xmlNodePtr *xml);
+int pcmk__log_output_new(pcmk__output_t **out);
+int pcmk__text_output_new(pcmk__output_t **out, const char *filename);
+
+/*!
+ * \internal
+ * \brief Select an updated return code for an operation on a \p pcmk__output_t
+ *
+ * This function helps to keep an up-to-date record of the most relevant return
+ * code from a series of operations on a \p pcmk__output_t object. For example,
+ * suppose the object has already produced some output, and we've saved a
+ * \p pcmk_rc_ok return code. A new operation did not produce any output and
+ * returned \p pcmk_rc_no_output. We can ignore the new \p pcmk_rc_no_output
+ * return code and keep the previous \p pcmk_rc_ok return code.
+ *
+ * It prioritizes return codes as follows (from highest to lowest priority):
+ * 1. Other return codes (unexpected errors)
+ * 2. \p pcmk_rc_ok
+ * 3. \p pcmk_rc_no_output
+ *
+ * \param[in] old_rc Saved return code from \p pcmk__output_t operations
+ * \param[in] new_rc New return code from a \p pcmk__output_t operation
+ *
+ * \retval \p old_rc \p new_rc is \p pcmk_rc_no_output, or \p new_rc is
+ * \p pcmk_rc_ok and \p old_rc is not \p pcmk_rc_no_output
+ * \retval \p new_rc Otherwise
+ */
+static inline int
+pcmk__output_select_rc(int old_rc, int new_rc)
+{
+ switch (new_rc) {
+ case pcmk_rc_no_output:
+ return old_rc;
+ case pcmk_rc_ok:
+ switch (old_rc) {
+ case pcmk_rc_no_output:
+ return new_rc;
+ default:
+ return old_rc;
+ }
+ default:
+ return new_rc;
+ }
+}
+
+#if defined(PCMK__UNIT_TESTING)
+/* If we are building libcrmcommon_test.a, add this accessor function so we can
+ * inspect the internal formatters hash table.
+ */
+GHashTable *pcmk__output_formatters(void);
+#endif
+
+#define PCMK__OUTPUT_SPACER_IF(out_obj, cond) \
+ if (cond) { \
+ out->spacer(out); \
+ }
+
+#define PCMK__OUTPUT_LIST_HEADER(out_obj, cond, retcode, title...) \
+ if (retcode == pcmk_rc_no_output) { \
+ PCMK__OUTPUT_SPACER_IF(out_obj, cond); \
+ retcode = pcmk_rc_ok; \
+ out_obj->begin_list(out_obj, NULL, NULL, title); \
+ }
+
+#define PCMK__OUTPUT_LIST_FOOTER(out_obj, retcode) \
+ if (retcode == pcmk_rc_ok) { \
+ out_obj->end_list(out_obj); \
+ }
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/crm/common/remote_internal.h b/include/crm/common/remote_internal.h
new file mode 100644
index 0000000..8473668
--- /dev/null
+++ b/include/crm/common/remote_internal.h
@@ -0,0 +1,48 @@
+/*
+ * 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.
+ */
+
+#ifndef PCMK__REMOTE_INTERNAL__H
+# define PCMK__REMOTE_INTERNAL__H
+
+// internal functions from remote.c
+
+typedef struct pcmk__remote_s pcmk__remote_t;
+
+int pcmk__remote_send_xml(pcmk__remote_t *remote, xmlNode *msg);
+int pcmk__remote_ready(const pcmk__remote_t *remote, int timeout_ms);
+int pcmk__read_remote_message(pcmk__remote_t *remote, int timeout_ms);
+xmlNode *pcmk__remote_message_xml(pcmk__remote_t *remote);
+int pcmk__connect_remote(const char *host, int port, int timeout_ms,
+ int *timer_id, int *sock_fd, void *userdata,
+ void (*callback) (void *userdata, int rc, int sock));
+int pcmk__accept_remote_connection(int ssock, int *csock);
+void pcmk__sockaddr2str(const void *sa, char *s);
+
+# ifdef HAVE_GNUTLS_GNUTLS_H
+# include <gnutls/gnutls.h>
+
+gnutls_session_t *pcmk__new_tls_session(int csock, unsigned int conn_type,
+ gnutls_credentials_type_t cred_type,
+ void *credentials);
+int pcmk__init_tls_dh(gnutls_dh_params_t *dh_params);
+int pcmk__read_handshake_data(const pcmk__client_t *client);
+
+/*!
+ * \internal
+ * \brief Perform client TLS handshake after establishing TCP socket
+ *
+ * \param[in,out] remote Newly established remote connection
+ * \param[in] timeout_ms Abort if handshake is not complete within this
+ *
+ * \return Standard Pacemaker return code
+ */
+int pcmk__tls_client_handshake(pcmk__remote_t *remote, int timeout_ms);
+
+# endif // HAVE_GNUTLS_GNUTLS_H
+#endif // PCMK__REMOTE_INTERNAL__H
diff --git a/include/crm/common/results.h b/include/crm/common/results.h
new file mode 100644
index 0000000..224bcbe
--- /dev/null
+++ b/include/crm/common/results.h
@@ -0,0 +1,393 @@
+/*
+ * Copyright 2012-2023 the Pacemaker project contributors
+ *
+ * The version control history for this file may have further details.
+ *
+ * This source code is licensed under the GNU Lesser General Public License
+ * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
+ */
+#ifndef PCMK__CRM_COMMON_RESULTS__H
+# define PCMK__CRM_COMMON_RESULTS__H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*!
+ * \file
+ * \brief Function and executable result codes
+ * \ingroup core
+ */
+
+// Lifted from config.h
+/* The _Noreturn keyword of C11. */
+#ifndef _Noreturn
+# if (defined __cplusplus \
+ && ((201103 <= __cplusplus && !(__GNUC__ == 4 && __GNUC_MINOR__ == 7)) \
+ || (defined _MSC_VER && 1900 <= _MSC_VER)))
+# define _Noreturn [[noreturn]]
+# elif ((!defined __cplusplus || defined __clang__) \
+ && (201112 <= (defined __STDC_VERSION__ ? __STDC_VERSION__ : 0) \
+ || 4 < __GNUC__ + (7 <= __GNUC_MINOR__)))
+ /* _Noreturn works as-is. */
+# elif 2 < __GNUC__ + (8 <= __GNUC_MINOR__) || 0x5110 <= __SUNPRO_C
+# define _Noreturn __attribute__ ((__noreturn__))
+# elif 1200 <= (defined _MSC_VER ? _MSC_VER : 0)
+# define _Noreturn __declspec (noreturn)
+# else
+# define _Noreturn
+# endif
+#endif
+
+# define CRM_ASSERT(expr) do { \
+ if (!(expr)) { \
+ crm_abort(__FILE__, __func__, __LINE__, #expr, TRUE, FALSE); \
+ abort(); /* crm_abort() doesn't always abort! */ \
+ } \
+ } while(0)
+
+/*
+ * Function return codes
+ *
+ * Most Pacemaker API functions return an integer return code. There are two
+ * alternative interpretations. The legacy interpration is that the absolute
+ * value of the return code is either a system error number or a custom
+ * pcmk_err_* number. This is less than ideal because system error numbers are
+ * constrained only to the positive int range, so there's the possibility that
+ * system errors and custom errors could collide (which did in fact happen
+ * already on one architecture). The new intepretation is that negative values
+ * are from the pcmk_rc_e enum, and positive values are system error numbers.
+ * Both use 0 for success.
+ *
+ * For system error codes, see:
+ * - /usr/include/asm-generic/errno.h
+ * - /usr/include/asm-generic/errno-base.h
+ */
+
+// Legacy custom return codes for Pacemaker API functions (deprecated)
+# define pcmk_ok 0
+# define PCMK_ERROR_OFFSET 190 /* Replacements on non-linux systems, see include/portability.h */
+# define PCMK_CUSTOM_OFFSET 200 /* Purely custom codes */
+# define pcmk_err_generic 201
+# define pcmk_err_no_quorum 202
+# define pcmk_err_schema_validation 203
+# define pcmk_err_transform_failed 204
+# define pcmk_err_old_data 205
+# define pcmk_err_diff_failed 206
+# define pcmk_err_diff_resync 207
+# define pcmk_err_cib_modified 208
+# define pcmk_err_cib_backup 209
+# define pcmk_err_cib_save 210
+# define pcmk_err_schema_unchanged 211
+# define pcmk_err_cib_corrupt 212
+# define pcmk_err_multiple 213
+# define pcmk_err_node_unknown 214
+# define pcmk_err_already 215
+/* On HPPA 215 is ENOSYM (Unknown error 215), which hopefully never happens. */
+#ifdef __hppa__
+# define pcmk_err_bad_nvpair 250 /* 216 is ENOTSOCK */
+# define pcmk_err_unknown_format 252 /* 217 is EDESTADDRREQ */
+#else
+# define pcmk_err_bad_nvpair 216
+# define pcmk_err_unknown_format 217
+#endif
+
+/*!
+ * \enum pcmk_rc_e
+ * \brief Return codes for Pacemaker API functions
+ *
+ * Any Pacemaker API function documented as returning a "standard Pacemaker
+ * return code" will return pcmk_rc_ok (0) on success, and one of this
+ * enumeration's other (negative) values or a (positive) system error number
+ * otherwise. The custom codes are at -1001 and lower, so that the caller may
+ * use -1 through -1000 for their own custom values if desired. While generally
+ * referred to as "errors", nonzero values simply indicate a result, which might
+ * or might not be an error depending on the calling context.
+ */
+enum pcmk_rc_e {
+ /* When adding new values, use consecutively lower numbers, update the array
+ * in lib/common/results.c, and test with crm_error.
+ */
+ pcmk_rc_bad_xml_patch = -1036,
+ pcmk_rc_bad_input = -1035,
+ pcmk_rc_disabled = -1034,
+ pcmk_rc_duplicate_id = -1033,
+ pcmk_rc_unpack_error = -1032,
+ pcmk_rc_invalid_transition = -1031,
+ pcmk_rc_graph_error = -1030,
+ pcmk_rc_dot_error = -1029,
+ pcmk_rc_underflow = -1028,
+ pcmk_rc_no_input = -1027,
+ pcmk_rc_no_output = -1026,
+ pcmk_rc_after_range = -1025,
+ pcmk_rc_within_range = -1024,
+ pcmk_rc_before_range = -1023,
+ pcmk_rc_undetermined = -1022,
+ pcmk_rc_op_unsatisfied = -1021,
+ pcmk_rc_ipc_pid_only = -1020,
+ pcmk_rc_ipc_unresponsive = -1019,
+ pcmk_rc_ipc_unauthorized = -1018,
+ pcmk_rc_no_quorum = -1017,
+ pcmk_rc_schema_validation = -1016,
+ pcmk_rc_schema_unchanged = -1015,
+ pcmk_rc_transform_failed = -1014,
+ pcmk_rc_old_data = -1013,
+ pcmk_rc_diff_failed = -1012,
+ pcmk_rc_diff_resync = -1011,
+ pcmk_rc_cib_modified = -1010,
+ pcmk_rc_cib_backup = -1009,
+ pcmk_rc_cib_save = -1008,
+ pcmk_rc_cib_corrupt = -1007,
+ pcmk_rc_multiple = -1006,
+ pcmk_rc_node_unknown = -1005,
+ pcmk_rc_already = -1004,
+ pcmk_rc_bad_nvpair = -1003,
+ pcmk_rc_unknown_format = -1002,
+ // Developers: Use a more specific code than pcmk_rc_error whenever possible
+ pcmk_rc_error = -1001,
+
+ // Values -1 through -1000 reserved for caller use
+
+ pcmk_rc_ok = 0
+
+ // Positive values reserved for system error numbers
+};
+
+
+/*!
+ * \enum ocf_exitcode
+ * \brief Exit status codes for resource agents
+ *
+ * The OCF Resource Agent API standard enumerates the possible exit status codes
+ * that agents should return. Besides being used with OCF agents, these values
+ * are also used by the executor as a universal status for all agent standards;
+ * actual results are mapped to these before returning them to clients.
+ */
+enum ocf_exitcode {
+ PCMK_OCF_OK = 0, //!< Success
+ PCMK_OCF_UNKNOWN_ERROR = 1, //!< Unspecified error
+ PCMK_OCF_INVALID_PARAM = 2, //!< Parameter invalid (in local context)
+ PCMK_OCF_UNIMPLEMENT_FEATURE = 3, //!< Requested action not implemented
+ PCMK_OCF_INSUFFICIENT_PRIV = 4, //!< Insufficient privileges
+ PCMK_OCF_NOT_INSTALLED = 5, //!< Dependencies not available locally
+ PCMK_OCF_NOT_CONFIGURED = 6, //!< Parameter invalid (inherently)
+ PCMK_OCF_NOT_RUNNING = 7, //!< Service safely stopped
+ PCMK_OCF_RUNNING_PROMOTED = 8, //!< Service active and promoted
+ PCMK_OCF_FAILED_PROMOTED = 9, //!< Service failed and possibly in promoted role
+ PCMK_OCF_DEGRADED = 190, //!< Service active but more likely to fail soon
+ PCMK_OCF_DEGRADED_PROMOTED = 191, //!< Service promoted but more likely to fail soon
+
+ /* These two are Pacemaker extensions, not in the OCF standard. The
+ * controller records PCMK_OCF_UNKNOWN for pending actions.
+ * PCMK_OCF_CONNECTION_DIED is used only with older DCs that don't support
+ * PCMK_EXEC_NOT_CONNECTED.
+ */
+ PCMK_OCF_CONNECTION_DIED = 189, //!< \deprecated See PCMK_EXEC_NOT_CONNECTED
+ PCMK_OCF_UNKNOWN = 193, //!< Action is pending
+
+#if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1)
+ // Former Pacemaker extensions
+ PCMK_OCF_EXEC_ERROR = 192, //!< \deprecated (Unused)
+ PCMK_OCF_SIGNAL = 194, //!< \deprecated (Unused)
+ PCMK_OCF_NOT_SUPPORTED = 195, //!< \deprecated (Unused)
+ PCMK_OCF_PENDING = 196, //!< \deprecated (Unused)
+ PCMK_OCF_CANCELLED = 197, //!< \deprecated (Unused)
+ PCMK_OCF_TIMEOUT = 198, //!< \deprecated (Unused)
+ PCMK_OCF_OTHER_ERROR = 199, //!< \deprecated (Unused)
+
+ //! \deprecated Use PCMK_OCF_RUNNING_PROMOTED instead
+ PCMK_OCF_RUNNING_MASTER = PCMK_OCF_RUNNING_PROMOTED,
+
+ //! \deprecated Use PCMK_OCF_FAILED_PROMOTED instead
+ PCMK_OCF_FAILED_MASTER = PCMK_OCF_FAILED_PROMOTED,
+
+ //! \deprecated Use PCMK_OCF_DEGRADED_PROMOTED instead
+ PCMK_OCF_DEGRADED_MASTER = PCMK_OCF_DEGRADED_PROMOTED,
+#endif
+};
+
+/*!
+ * \enum crm_exit_e
+ * \brief Exit status codes for tools and daemons
+ *
+ * We want well-specified (i.e. OS-invariant) exit status codes for our daemons
+ * and applications so they can be relied on by callers. (Function return codes
+ * and errno's do not make good exit statuses.)
+ *
+ * The only hard rule is that exit statuses must be between 0 and 255; all else
+ * is convention. Universally, 0 is success, and 1 is generic error (excluding
+ * OSes we don't support -- for example, OpenVMS considers 1 success!).
+ *
+ * For init scripts, the LSB gives meaning to 0-7, and sets aside 150-199 for
+ * application use. OCF adds 8-9 and 190-191.
+ *
+ * sysexits.h was an attempt to give additional meanings, but never really
+ * caught on. It uses 0 and 64-78.
+ *
+ * Bash reserves 2 ("incorrect builtin usage") and 126-255 (126 is "command
+ * found but not executable", 127 is "command not found", 128 + n is
+ * "interrupted by signal n").
+ *
+ * tldp.org recommends 64-113 for application use.
+ *
+ * We try to overlap with the above conventions when practical.
+ */
+typedef enum crm_exit_e {
+ // Common convention
+ CRM_EX_OK = 0, //!< Success
+ CRM_EX_ERROR = 1, //!< Unspecified error
+
+ // LSB + OCF
+ CRM_EX_INVALID_PARAM = 2, //!< Parameter invalid (in local context)
+ CRM_EX_UNIMPLEMENT_FEATURE = 3, //!< Requested action not implemented
+ CRM_EX_INSUFFICIENT_PRIV = 4, //!< Insufficient privileges
+ CRM_EX_NOT_INSTALLED = 5, //!< Dependencies not available locally
+ CRM_EX_NOT_CONFIGURED = 6, //!< Parameter invalid (inherently)
+ CRM_EX_NOT_RUNNING = 7, //!< Service safely stopped
+ CRM_EX_PROMOTED = 8, //!< Service active and promoted
+ CRM_EX_FAILED_PROMOTED = 9, //!< Service failed and possibly promoted
+
+ // sysexits.h
+ CRM_EX_USAGE = 64, //!< Command line usage error
+ CRM_EX_DATAERR = 65, //!< User-supplied data incorrect
+ CRM_EX_NOINPUT = 66, //!< Input file not available
+ CRM_EX_NOUSER = 67, //!< User does not exist
+ CRM_EX_NOHOST = 68, //!< Host unknown
+ CRM_EX_UNAVAILABLE = 69, //!< Needed service unavailable
+ CRM_EX_SOFTWARE = 70, //!< Internal software bug
+ CRM_EX_OSERR = 71, //!< External (OS/environmental) problem
+ CRM_EX_OSFILE = 72, //!< System file not usable
+ CRM_EX_CANTCREAT = 73, //!< File couldn't be created
+ CRM_EX_IOERR = 74, //!< File I/O error
+ CRM_EX_TEMPFAIL = 75, //!< Try again
+ CRM_EX_PROTOCOL = 76, //!< Protocol violated
+ CRM_EX_NOPERM = 77, //!< Non-file permission issue
+ CRM_EX_CONFIG = 78, //!< Misconfiguration
+
+ // Custom
+ CRM_EX_FATAL = 100, //!< Do not respawn
+ CRM_EX_PANIC = 101, //!< Panic the local host
+ CRM_EX_DISCONNECT = 102, //!< Lost connection to something
+ CRM_EX_OLD = 103, //!< Update older than existing config
+ CRM_EX_DIGEST = 104, //!< Digest comparison failed
+ CRM_EX_NOSUCH = 105, //!< Requested item does not exist
+ CRM_EX_QUORUM = 106, //!< Local partition does not have quorum
+ CRM_EX_UNSAFE = 107, //!< Requires --force or new conditions
+ CRM_EX_EXISTS = 108, //!< Requested item already exists
+ CRM_EX_MULTIPLE = 109, //!< Requested item has multiple matches
+ CRM_EX_EXPIRED = 110, //!< Requested item has expired
+ CRM_EX_NOT_YET_IN_EFFECT = 111, //!< Requested item is not in effect
+ CRM_EX_INDETERMINATE = 112, //!< Could not determine status
+ CRM_EX_UNSATISFIED = 113, //!< Requested item does not satisfy constraints
+
+ // Other
+ CRM_EX_TIMEOUT = 124, //!< Convention from timeout(1)
+
+ /* Anything above 128 overlaps with some shells' use of these values for
+ * "interrupted by signal N", and so may be unreliable when detected by
+ * shell scripts.
+ */
+
+ // OCF Resource Agent API 1.1
+ CRM_EX_DEGRADED = 190, //!< Service active but more likely to fail soon
+ CRM_EX_DEGRADED_PROMOTED = 191, //!< Service promoted but more likely to fail soon
+
+ /* Custom
+ *
+ * This can be used to initialize exit status variables or to indicate that
+ * a command is pending (which is what the controller uses it for).
+ */
+ CRM_EX_NONE = 193, //!< No exit status available
+
+ CRM_EX_MAX = 255, //!< Ensure crm_exit_t can hold this
+} crm_exit_t;
+
+/*!
+ * \enum pcmk_exec_status
+ * \brief Execution status
+ *
+ * These codes are used to specify the result of the attempt to execute an
+ * agent, rather than the agent's result itself.
+ */
+enum pcmk_exec_status {
+ PCMK_EXEC_UNKNOWN = -2, //!< Used only to initialize variables
+ PCMK_EXEC_PENDING = -1, //!< Action is in progress
+ PCMK_EXEC_DONE, //!< Action completed, result is known
+ PCMK_EXEC_CANCELLED, //!< Action was cancelled
+ PCMK_EXEC_TIMEOUT, //!< Action did not complete in time
+ PCMK_EXEC_NOT_SUPPORTED, //!< Agent does not implement requested action
+ PCMK_EXEC_ERROR, //!< Execution failed, may be retried
+ PCMK_EXEC_ERROR_HARD, //!< Execution failed, do not retry on node
+ PCMK_EXEC_ERROR_FATAL, //!< Execution failed, do not retry anywhere
+ PCMK_EXEC_NOT_INSTALLED, //!< Agent or dependency not available locally
+ PCMK_EXEC_NOT_CONNECTED, //!< No connection to executor
+ PCMK_EXEC_INVALID, //!< Action cannot be attempted (e.g. shutdown)
+ PCMK_EXEC_NO_FENCE_DEVICE, //!< No fence device is configured for target
+ PCMK_EXEC_NO_SECRETS, //!< Necessary CIB secrets are unavailable
+
+ // Add new values above here then update this one below
+ PCMK_EXEC_MAX = PCMK_EXEC_NO_SECRETS, //!< Maximum value for this enum
+};
+
+/*!
+ * \enum pcmk_result_type
+ * \brief Types of Pacemaker result codes
+ *
+ * A particular integer can have different meanings within different Pacemaker
+ * result code families. It may be interpretable within zero, one, or multiple
+ * families.
+ *
+ * These values are useful for specifying how an integer result code should be
+ * interpreted in situations involving a generic integer value. For example, a
+ * function that can process multiple types of result codes might accept an
+ * arbitrary integer argument along with a \p pcmk_result_type argument that
+ * specifies how to interpret the integer.
+ */
+enum pcmk_result_type {
+ pcmk_result_legacy = 0, //!< Legacy API function return code
+ pcmk_result_rc = 1, //!< Standard Pacemaker return code
+ pcmk_result_exitcode = 2, //!< Exit status code
+ pcmk_result_exec_status = 3, //!< Execution status
+};
+
+int pcmk_result_get_strings(int code, enum pcmk_result_type type,
+ const char **name, const char **desc);
+const char *pcmk_rc_name(int rc);
+const char *pcmk_rc_str(int rc);
+crm_exit_t pcmk_rc2exitc(int rc);
+enum ocf_exitcode pcmk_rc2ocf(int rc);
+int pcmk_rc2legacy(int rc);
+int pcmk_legacy2rc(int legacy_rc);
+const char *pcmk_strerror(int rc);
+const char *pcmk_errorname(int rc);
+const char *bz2_strerror(int rc);
+const char *crm_exit_name(crm_exit_t exit_code);
+const char *crm_exit_str(crm_exit_t exit_code);
+_Noreturn crm_exit_t crm_exit(crm_exit_t rc);
+
+static inline const char *
+pcmk_exec_status_str(enum pcmk_exec_status status)
+{
+ switch (status) {
+ case PCMK_EXEC_PENDING: return "pending";
+ case PCMK_EXEC_DONE: return "complete";
+ case PCMK_EXEC_CANCELLED: return "Cancelled";
+ case PCMK_EXEC_TIMEOUT: return "Timed Out";
+ case PCMK_EXEC_NOT_SUPPORTED: return "NOT SUPPORTED";
+ case PCMK_EXEC_ERROR: return "Error";
+ case PCMK_EXEC_ERROR_HARD: return "Hard error";
+ case PCMK_EXEC_ERROR_FATAL: return "Fatal error";
+ case PCMK_EXEC_NOT_INSTALLED: return "Not installed";
+ case PCMK_EXEC_NOT_CONNECTED: return "Internal communication failure";
+ case PCMK_EXEC_INVALID: return "Cannot execute now";
+ case PCMK_EXEC_NO_FENCE_DEVICE: return "No fence device";
+ case PCMK_EXEC_NO_SECRETS: return "CIB secrets unavailable";
+ default: return "UNKNOWN!";
+ }
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/crm/common/results_compat.h b/include/crm/common/results_compat.h
new file mode 100644
index 0000000..00ac6b2
--- /dev/null
+++ b/include/crm/common/results_compat.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright 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 PCMK__CRM_COMMON_RESULTS_COMPAT__H
+# define PCMK__CRM_COMMON_RESULTS_COMPAT__H
+
+#include <crm/common/results.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * \file
+ * \brief Deprecated Pacemaker results API
+ * \ingroup core
+ * \deprecated Do not include this header directly. The result APIs in this
+ * header, and the header itself, will be removed in a future
+ * release.
+ */
+
+//! \deprecated Use pcmk_rc2exitc(pcmk_legacy2rc(rc)) instead
+crm_exit_t crm_errno2exit(int rc);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // PCMK__CRM_COMMON_MAINLOOP_COMPAT__H
diff --git a/include/crm/common/results_internal.h b/include/crm/common/results_internal.h
new file mode 100644
index 0000000..be62780
--- /dev/null
+++ b/include/crm/common/results_internal.h
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2020-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 PCMK__COMMON_RESULTS_INTERNAL__H
+#define PCMK__COMMON_RESULTS_INTERNAL__H
+
+#include <glib.h> // GQuark
+
+// Generic result code type
+
+int pcmk__result_bounds(enum pcmk_result_type, int *lower, int *upper);
+
+// Standard Pacemaker API return codes
+
+extern const size_t pcmk__n_rc;
+
+/* Error domains for use with g_set_error */
+
+GQuark pcmk__rc_error_quark(void);
+GQuark pcmk__exitc_error_quark(void);
+
+#define PCMK__RC_ERROR pcmk__rc_error_quark()
+#define PCMK__EXITC_ERROR pcmk__exitc_error_quark()
+
+/* Action results */
+
+typedef struct {
+ int exit_status; // Child exit status
+ enum pcmk_exec_status execution_status; // Execution status
+ char *exit_reason; // Brief, human-friendly explanation
+ char *action_stdout; // Action output
+ char *action_stderr; // Action error output
+} pcmk__action_result_t;
+
+/*!
+ * \internal
+ * \brief Static initialization for an action result
+ *
+ * \note Importantly, this ensures pcmk__reset_result() won't try to free
+ * garbage.
+ */
+#define PCMK__UNKNOWN_RESULT { \
+ .exit_status = CRM_EX_OK, \
+ .execution_status = PCMK_EXEC_UNKNOWN, \
+ .exit_reason = NULL, \
+ .action_stdout = NULL, \
+ .action_stderr = NULL, \
+ }
+
+void pcmk__set_result(pcmk__action_result_t *result, int exit_status,
+ enum pcmk_exec_status exec_status,
+ const char *exit_reason);
+
+void pcmk__format_result(pcmk__action_result_t *result, int exit_status,
+ enum pcmk_exec_status exec_status,
+ const char *format, ...) G_GNUC_PRINTF(4, 5);
+
+void pcmk__set_result_output(pcmk__action_result_t *result,
+ char *out, char *err);
+
+void pcmk__reset_result(pcmk__action_result_t *result);
+
+void pcmk__copy_result(const pcmk__action_result_t *src,
+ pcmk__action_result_t *dst);
+
+/*!
+ * \internal
+ * \brief Check whether a result is OK
+ *
+ * \param[in] result
+ *
+ * \return true if the result's exit status is CRM_EX_OK and its
+ * execution status is PCMK_EXEC_DONE, otherwise false
+ */
+static inline bool
+pcmk__result_ok(const pcmk__action_result_t *result)
+{
+ return (result != NULL) && (result->exit_status == CRM_EX_OK)
+ && (result->execution_status == PCMK_EXEC_DONE);
+}
+
+#endif // PCMK__COMMON_RESULTS_INTERNAL__H
diff --git a/include/crm/common/strings_internal.h b/include/crm/common/strings_internal.h
new file mode 100644
index 0000000..cd394d9
--- /dev/null
+++ b/include/crm/common/strings_internal.h
@@ -0,0 +1,214 @@
+/*
+ * Copyright 2015-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 PCMK__STRINGS_INTERNAL__H
+#define PCMK__STRINGS_INTERNAL__H
+
+#include <stdbool.h> // bool
+
+#include <glib.h> // guint, GList, GHashTable
+
+/* internal constants for generic string functions (from strings.c) */
+
+#define PCMK__PARSE_INT_DEFAULT -1
+#define PCMK__PARSE_DBL_DEFAULT -1.0
+
+/* internal generic string functions (from strings.c) */
+
+enum pcmk__str_flags {
+ pcmk__str_none = 0,
+ pcmk__str_casei = 1 << 0,
+ pcmk__str_null_matches = 1 << 1,
+ pcmk__str_regex = 1 << 2,
+ pcmk__str_star_matches = 1 << 3,
+};
+
+int pcmk__scan_double(const char *text, double *result,
+ const char *default_text, char **end_text);
+int pcmk__guint_from_hash(GHashTable *table, const char *key, guint default_val,
+ guint *result);
+bool pcmk__starts_with(const char *str, const char *prefix);
+bool pcmk__ends_with(const char *s, const char *match);
+bool pcmk__ends_with_ext(const char *s, const char *match);
+char *pcmk__trim(char *str);
+void pcmk__add_separated_word(GString **list, size_t init_size,
+ const char *word, const char *separator);
+int pcmk__compress(const char *data, unsigned int length, unsigned int max,
+ char **result, unsigned int *result_len);
+
+int pcmk__scan_ll(const char *text, long long *result, long long default_value);
+int pcmk__scan_min_int(const char *text, int *result, int minimum);
+int pcmk__scan_port(const char *text, int *port);
+int pcmk__parse_ll_range(const char *srcstring, long long *start, long long *end);
+
+GHashTable *pcmk__strkey_table(GDestroyNotify key_destroy_func,
+ GDestroyNotify value_destroy_func);
+GHashTable *pcmk__strikey_table(GDestroyNotify key_destroy_func,
+ GDestroyNotify value_destroy_func);
+GHashTable *pcmk__str_table_dup(GHashTable *old_table);
+
+/*!
+ * \internal
+ * \brief Get a string value with a default if NULL
+ *
+ * \param[in] s String to return if non-NULL
+ * \param[in] default_value String (or NULL) to return if \p s is NULL
+ *
+ * \return \p s if \p s is non-NULL, otherwise \p default_value
+ */
+static inline const char *
+pcmk__s(const char *s, const char *default_value)
+{
+ return (s == NULL)? default_value : s;
+}
+
+/*!
+ * \internal
+ * \brief Create a hash table with integer keys
+ *
+ * \param[in] value_destroy_func Function to free a value
+ *
+ * \return Newly allocated hash table
+ * \note It is the caller's responsibility to free the result, using
+ * g_hash_table_destroy().
+ */
+static inline GHashTable *
+pcmk__intkey_table(GDestroyNotify value_destroy_func)
+{
+ return g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL,
+ value_destroy_func);
+}
+
+/*!
+ * \internal
+ * \brief Insert a value into a hash table with integer keys
+ *
+ * \param[in,out] hash_table Table to insert into
+ * \param[in] key Integer key to insert
+ * \param[in] value Value to insert
+ *
+ * \return Whether the key/value was already in the table
+ * \note This has the same semantics as g_hash_table_insert(). If the key
+ * already exists in the table, the old value is freed and replaced.
+ */
+static inline gboolean
+pcmk__intkey_table_insert(GHashTable *hash_table, int key, gpointer value)
+{
+ return g_hash_table_insert(hash_table, GINT_TO_POINTER(key), value);
+}
+
+/*!
+ * \internal
+ * \brief Look up a value in a hash table with integer keys
+ *
+ * \param[in] hash_table Table to check
+ * \param[in] key Integer key to look for
+ *
+ * \return Value in table for \key (or NULL if not found)
+ */
+static inline gpointer
+pcmk__intkey_table_lookup(GHashTable *hash_table, int key)
+{
+ return g_hash_table_lookup(hash_table, GINT_TO_POINTER(key));
+}
+
+/*!
+ * \internal
+ * \brief Remove a key/value from a hash table with integer keys
+ *
+ * \param[in,out] hash_table Table to modify
+ * \param[in] key Integer key of entry to remove
+ *
+ * \return Whether \p key was found and removed from \p hash_table
+ */
+static inline gboolean
+pcmk__intkey_table_remove(GHashTable *hash_table, int key)
+{
+ return g_hash_table_remove(hash_table, GINT_TO_POINTER(key));
+}
+
+gboolean pcmk__str_in_list(const gchar *s, const GList *lst, uint32_t flags);
+
+bool pcmk__strcase_any_of(const char *s, ...) G_GNUC_NULL_TERMINATED;
+bool pcmk__str_any_of(const char *s, ...) G_GNUC_NULL_TERMINATED;
+bool pcmk__char_in_any_str(int ch, ...) G_GNUC_NULL_TERMINATED;
+
+int pcmk__strcmp(const char *s1, const char *s2, uint32_t flags);
+int pcmk__numeric_strcasecmp(const char *s1, const char *s2);
+void pcmk__str_update(char **str, const char *value);
+void pcmk__g_strcat(GString *buffer, ...) G_GNUC_NULL_TERMINATED;
+
+static inline bool
+pcmk__str_eq(const char *s1, const char *s2, uint32_t flags)
+{
+ return pcmk__strcmp(s1, s2, flags) == 0;
+}
+
+// Like pcmk__add_separated_word() but using a space as separator
+static inline void
+pcmk__add_word(GString **list, size_t init_size, const char *word)
+{
+ return pcmk__add_separated_word(list, init_size, word, " ");
+}
+
+/* Correctly displaying singular or plural is complicated; consider "1 node has"
+ * vs. "2 nodes have". A flexible solution is to pluralize entire strings, e.g.
+ *
+ * if (a == 1) {
+ * crm_info("singular message"):
+ * } else {
+ * crm_info("plural message");
+ * }
+ *
+ * though even that's not sufficient for all languages besides English (if we
+ * ever desire to do translations of output and log messages). But the following
+ * convenience macros are "good enough" and more concise for many cases.
+ */
+
+/* Example:
+ * crm_info("Found %d %s", nentries,
+ * pcmk__plural_alt(nentries, "entry", "entries"));
+ */
+#define pcmk__plural_alt(i, s1, s2) (((i) == 1)? (s1) : (s2))
+
+// Example: crm_info("Found %d node%s", nnodes, pcmk__plural_s(nnodes));
+#define pcmk__plural_s(i) pcmk__plural_alt(i, "", "s")
+
+static inline int
+pcmk__str_empty(const char *s)
+{
+ return (s == NULL) || (s[0] == '\0');
+}
+
+static inline char *
+pcmk__itoa(int an_int)
+{
+ return crm_strdup_printf("%d", an_int);
+}
+
+static inline char *
+pcmk__ftoa(double a_float)
+{
+ return crm_strdup_printf("%f", a_float);
+}
+
+static inline char *
+pcmk__ttoa(time_t epoch_time)
+{
+ return crm_strdup_printf("%lld", (long long) epoch_time);
+}
+
+// note this returns const not allocated
+static inline const char *
+pcmk__btoa(bool condition)
+{
+ return condition? "true" : "false";
+}
+
+#endif /* PCMK__STRINGS_INTERNAL__H */
diff --git a/include/crm/common/unittest_internal.h b/include/crm/common/unittest_internal.h
new file mode 100644
index 0000000..b8f78cf
--- /dev/null
+++ b/include/crm/common/unittest_internal.h
@@ -0,0 +1,84 @@
+/*
+ * Copyright 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 <signal.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <setjmp.h>
+#include <sys/resource.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <cmocka.h>
+
+#ifndef CRM_COMMON_UNITTEST_INTERNAL__H
+#define CRM_COMMON_UNITTEST_INTERNAL__H
+
+/* internal unit testing related utilities */
+
+/*!
+ * \internal
+ * \brief Assert that a statement aborts through CRM_ASSERT().
+ *
+ * \param[in] stmt Statement to execute; can be an expression.
+ *
+ * A cmocka-like assert macro for use in unit testing. This one verifies that a
+ * statement aborts through CRM_ASSERT(), erroring out if that is not the case.
+ *
+ * This macro works by running the statement in a forked child process with core
+ * dumps disabled (CRM_ASSERT() calls \c abort(), which will write out a core
+ * dump). The parent waits for the child to exit and checks why. If the child
+ * received a \c SIGABRT, the test passes. For all other cases, the test fails.
+ *
+ * \note If cmocka's expect_*() or will_return() macros are called along with
+ * pcmk__assert_asserts(), they must be called within a block that is
+ * passed as the \c stmt argument. That way, the values are added only to
+ * the child's queue. Otherwise, values added to the parent's queue will
+ * never be popped, and the test will fail.
+ */
+#define pcmk__assert_asserts(stmt) \
+ do { \
+ pid_t p = fork(); \
+ if (p == 0) { \
+ struct rlimit cores = { 0, 0 }; \
+ setrlimit(RLIMIT_CORE, &cores); \
+ stmt; \
+ _exit(0); \
+ } else if (p > 0) { \
+ int wstatus = 0; \
+ if (waitpid(p, &wstatus, 0) == -1) { \
+ fail_msg("waitpid failed"); \
+ } \
+ if (!(WIFSIGNALED(wstatus) && WTERMSIG(wstatus) == SIGABRT)) { \
+ fail_msg("statement terminated in child without asserting"); \
+ } \
+ } else { \
+ fail_msg("unable to fork for assert test"); \
+ } \
+ } while (0);
+
+/* Generate the main function of most unit test files. Typically, group_setup
+ * and group_teardown will be NULL. The rest of the arguments are a list of
+ * calls to cmocka_unit_test or cmocka_unit_test_setup_teardown to run the
+ * individual unit tests.
+ */
+#define PCMK__UNIT_TEST(group_setup, group_teardown, ...) \
+int \
+main(int argc, char **argv) \
+{ \
+ const struct CMUnitTest t[] = { \
+ __VA_ARGS__ \
+ }; \
+ cmocka_set_message_output(CM_OUTPUT_TAP); \
+ return cmocka_run_group_tests(t, group_setup, group_teardown); \
+}
+
+#endif /* CRM_COMMON_UNITTEST_INTERNAL__H */
diff --git a/include/crm/common/util.h b/include/crm/common/util.h
new file mode 100644
index 0000000..8acdff9
--- /dev/null
+++ b/include/crm/common/util.h
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2004-2023 the Pacemaker project contributors
+ *
+ * The version control history for this file may have further details.
+ *
+ * This source code is licensed under the GNU Lesser General Public License
+ * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
+ */
+
+#ifndef PCMK__CRM_COMMON_UTIL__H
+# define PCMK__CRM_COMMON_UTIL__H
+
+# include <sys/types.h> // gid_t, mode_t, size_t, time_t, uid_t
+# include <stdlib.h>
+# include <stdbool.h>
+# include <stdint.h> // uint32_t
+# include <limits.h>
+# include <signal.h>
+# include <glib.h>
+
+# include <libxml/tree.h>
+
+# include <crm/lrmd.h>
+# include <crm/common/acl.h>
+# include <crm/common/agents.h>
+# include <crm/common/results.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * \file
+ * \brief Utility functions
+ * \ingroup core
+ */
+
+
+# define ONLINESTATUS "online" // Status of an online client
+# define OFFLINESTATUS "offline" // Status of an offline client
+
+/* public node attribute functions (from attrd_client.c) */
+char *pcmk_promotion_score_name(const char *rsc_id);
+
+/* public Pacemaker Remote functions (from remote.c) */
+int crm_default_remote_port(void);
+
+/* public score-related functions (from scores.c) */
+const char *pcmk_readable_score(int score);
+int char2score(const char *score);
+int pcmk__add_scores(int score1, int score2);
+
+/* public string functions (from strings.c) */
+gboolean crm_is_true(const char *s);
+int crm_str_to_boolean(const char *s, int *ret);
+long long crm_get_msec(const char *input);
+char * crm_strip_trailing_newline(char *str);
+char *crm_strdup_printf(char const *format, ...) G_GNUC_PRINTF(1, 2);
+
+guint crm_parse_interval_spec(const char *input);
+
+/* public operation functions (from operations.c) */
+gboolean parse_op_key(const char *key, char **rsc_id, char **op_type,
+ guint *interval_ms);
+gboolean decode_transition_key(const char *key, char **uuid, int *transition_id,
+ int *action_id, int *target_rc);
+gboolean decode_transition_magic(const char *magic, char **uuid,
+ int *transition_id, int *action_id,
+ int *op_status, int *op_rc, int *target_rc);
+int rsc_op_expected_rc(const lrmd_event_data_t *event);
+gboolean did_rsc_op_fail(lrmd_event_data_t *event, int target_rc);
+bool crm_op_needs_metadata(const char *rsc_class, const char *op);
+xmlNode *crm_create_op_xml(xmlNode *parent, const char *prefix,
+ const char *task, const char *interval_spec,
+ const char *timeout);
+#define CRM_DEFAULT_OP_TIMEOUT_S "20s"
+
+bool pcmk_is_probe(const char *task, guint interval);
+bool pcmk_xe_is_probe(const xmlNode *xml_op);
+bool pcmk_xe_mask_probe_failure(const xmlNode *xml_op);
+
+int compare_version(const char *version1, const char *version2);
+
+/* coverity[+kill] */
+void crm_abort(const char *file, const char *function, int line,
+ const char *condition, gboolean do_core, gboolean do_fork);
+
+/*!
+ * \brief Check whether any of specified flags are set in a flag group
+ *
+ * \param[in] flag_group The flag group being examined
+ * \param[in] flags_to_check Which flags in flag_group should be checked
+ *
+ * \return true if \p flags_to_check is nonzero and any of its flags are set in
+ * \p flag_group, or false otherwise
+ */
+static inline bool
+pcmk_any_flags_set(uint64_t flag_group, uint64_t flags_to_check)
+{
+ return (flag_group & flags_to_check) != 0;
+}
+
+/*!
+ * \brief Check whether all of specified flags are set in a flag group
+ *
+ * \param[in] flag_group The flag group being examined
+ * \param[in] flags_to_check Which flags in flag_group should be checked
+ *
+ * \return true if \p flags_to_check is zero or all of its flags are set in
+ * \p flag_group, or false otherwise
+ */
+static inline bool
+pcmk_all_flags_set(uint64_t flag_group, uint64_t flags_to_check)
+{
+ return (flag_group & flags_to_check) == flags_to_check;
+}
+
+/*!
+ * \brief Convenience alias for pcmk_all_flags_set(), to check single flag
+ */
+#define pcmk_is_set(g, f) pcmk_all_flags_set((g), (f))
+
+char *crm_meta_name(const char *field);
+const char *crm_meta_value(GHashTable * hash, const char *field);
+
+char *crm_md5sum(const char *buffer);
+
+char *crm_generate_uuid(void);
+
+// This belongs in ipc.h but is here for backward compatibility
+bool crm_is_daemon_name(const char *name);
+
+int crm_user_lookup(const char *name, uid_t * uid, gid_t * gid);
+int pcmk_daemon_user(uid_t *uid, gid_t *gid);
+
+#ifdef HAVE_GNUTLS_GNUTLS_H
+void crm_gnutls_global_init(void);
+#endif
+
+char *pcmk_hostname(void);
+
+bool pcmk_str_is_infinity(const char *s);
+bool pcmk_str_is_minus_infinity(const char *s);
+
+#if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1)
+#include <crm/common/util_compat.h>
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/crm/common/util_compat.h b/include/crm/common/util_compat.h
new file mode 100644
index 0000000..9e02e12
--- /dev/null
+++ b/include/crm/common/util_compat.h
@@ -0,0 +1,164 @@
+/*
+ * Copyright 2004-2021 the Pacemaker project contributors
+ *
+ * The version control history for this file may have further details.
+ *
+ * This source code is licensed under the GNU Lesser General Public License
+ * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
+ */
+
+#ifndef PCMK__CRM_COMMON_UTIL_COMPAT__H
+# define PCMK__CRM_COMMON_UTIL_COMPAT__H
+
+# include <glib.h>
+# include <crm/common/util.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * \file
+ * \brief Deprecated Pacemaker utilities
+ * \ingroup core
+ * \deprecated Do not include this header directly. The utilities in this
+ * header, and the header itself, will be removed in a future
+ * release.
+ */
+
+//! \deprecated Use crm_parse_interval_spec() instead
+#define crm_get_interval crm_parse_interval_spec
+
+//! \deprecated Use !pcmk_is_set() or !pcmk_all_flags_set() instead
+static inline gboolean
+is_not_set(long long word, long long bit)
+{
+ return ((word & bit) == 0);
+}
+
+//! \deprecated Use pcmk_is_set() or pcmk_all_flags_set() instead
+static inline gboolean
+is_set(long long word, long long bit)
+{
+ return ((word & bit) == bit);
+}
+
+//! \deprecated Use pcmk_any_flags_set() instead
+static inline gboolean
+is_set_any(long long word, long long bit)
+{
+ return ((word & bit) != 0);
+}
+
+//! \deprecated Use strcmp() or strcasecmp() instead
+gboolean crm_str_eq(const char *a, const char *b, gboolean use_case);
+
+//! \deprecated Use strcmp() instead
+gboolean safe_str_neq(const char *a, const char *b);
+
+//! \deprecated Use strcasecmp() instead
+#define safe_str_eq(a, b) crm_str_eq(a, b, FALSE)
+
+//! \deprecated Use snprintf() instead
+char *crm_itoa_stack(int an_int, char *buf, size_t len);
+
+//! \deprecated Use sscanf() instead
+int pcmk_scan_nvpair(const char *input, char **name, char **value);
+
+//! \deprecated Use a standard printf()-style function instead
+char *pcmk_format_nvpair(const char *name, const char *value,
+ const char *units);
+
+//! \deprecated Use a standard printf()-style function instead
+char *pcmk_format_named_time(const char *name, time_t epoch_time);
+
+//! \deprecated Use strtoll() instead
+long long crm_parse_ll(const char *text, const char *default_text);
+
+//! \deprecated Use strtoll() instead
+int crm_parse_int(const char *text, const char *default_text);
+
+//! \deprecated Use strtoll() instead
+# define crm_atoi(text, default_text) crm_parse_int(text, default_text)
+
+//! \deprecated Use g_str_hash() instead
+guint g_str_hash_traditional(gconstpointer v);
+
+//! \deprecated Use g_str_hash() instead
+#define crm_str_hash g_str_hash_traditional
+
+//! \deprecated Do not use Pacemaker for generic string comparison
+gboolean crm_strcase_equal(gconstpointer a, gconstpointer b);
+
+//! \deprecated Do not use Pacemaker for generic string manipulation
+guint crm_strcase_hash(gconstpointer v);
+
+//! \deprecated Use g_hash_table_new_full() instead
+static inline GHashTable *
+crm_str_table_new(void)
+{
+ return g_hash_table_new_full(crm_str_hash, g_str_equal, free, free);
+}
+
+//! \deprecated Use g_hash_table_new_full() instead
+static inline GHashTable *
+crm_strcase_table_new(void)
+{
+ return g_hash_table_new_full(crm_strcase_hash, crm_strcase_equal,
+ free, free);
+}
+
+//! \deprecated Do not use Pacemaker for generic hash table manipulation
+GHashTable *crm_str_table_dup(GHashTable *old_table);
+
+//! \deprecated Use g_hash_able_size() instead
+static inline guint
+crm_hash_table_size(GHashTable *hashtable)
+{
+ if (hashtable == NULL) {
+ return 0;
+ }
+ return g_hash_table_size(hashtable);
+}
+
+//! \deprecated Don't use Pacemaker for string manipulation
+char *crm_strip_trailing_newline(char *str);
+
+//! \deprecated Don't use Pacemaker for string manipulation
+int pcmk_numeric_strcasecmp(const char *s1, const char *s2);
+
+//! \deprecated Don't use Pacemaker for string manipulation
+static inline char *
+crm_itoa(int an_int)
+{
+ return crm_strdup_printf("%d", an_int);
+}
+
+//! \deprecated Don't use Pacemaker for string manipulation
+static inline char *
+crm_ftoa(double a_float)
+{
+ return crm_strdup_printf("%f", a_float);
+}
+
+//! \deprecated Don't use Pacemaker for string manipulation
+static inline char *
+crm_ttoa(time_t epoch_time)
+{
+ return crm_strdup_printf("%lld", (long long) epoch_time);
+}
+
+//! \deprecated Do not use Pacemaker libraries for generic I/O
+void crm_build_path(const char *path_c, mode_t mode);
+
+//! \deprecated Use pcmk_readable_score() instead
+char *score2char(int score);
+
+//! \deprecated Use pcmk_readable_score() instead
+char *score2char_stack(int score, char *buf, size_t len);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // PCMK__CRM_COMMON_UTIL_COMPAT__H
diff --git a/include/crm/common/xml.h b/include/crm/common/xml.h
new file mode 100644
index 0000000..682b31c
--- /dev/null
+++ b/include/crm/common/xml.h
@@ -0,0 +1,306 @@
+/*
+ * Copyright 2004-2023 the Pacemaker project contributors
+ *
+ * The version control history for this file may have further details.
+ *
+ * This source code is licensed under the GNU Lesser General Public License
+ * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
+ */
+
+#ifndef PCMK__CRM_COMMON_XML__H
+# define PCMK__CRM_COMMON_XML__H
+
+
+# include <stdio.h>
+# include <sys/types.h>
+# include <unistd.h>
+
+# include <stdlib.h>
+# include <errno.h>
+# include <fcntl.h>
+
+# include <libxml/tree.h>
+# include <libxml/xpath.h>
+
+# include <crm/crm.h>
+# include <crm/common/nvpair.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * \file
+ * \brief Wrappers for and extensions to libxml2
+ * \ingroup core
+ */
+
+/* Define compression parameters for IPC messages
+ *
+ * Compression costs a LOT, so we don't want to do it unless we're hitting
+ * message limits. Currently, we use 128KB as the threshold, because higher
+ * values don't play well with the heartbeat stack. With an earlier limit of
+ * 10KB, compressing 184 of 1071 messages accounted for 23% of the total CPU
+ * used by the cib.
+ */
+# define CRM_BZ2_BLOCKS 4
+# define CRM_BZ2_WORK 20
+# define CRM_BZ2_THRESHOLD 128 * 1024
+
+typedef const xmlChar *pcmkXmlStr;
+
+gboolean add_message_xml(xmlNode * msg, const char *field, xmlNode * xml);
+xmlNode *get_message_xml(const xmlNode *msg, const char *field);
+
+xmlDoc *getDocPtr(xmlNode * node);
+
+/*
+ * \brief xmlCopyPropList ACLs-sensitive replacement expading i++ notation
+ *
+ * The gist is the same as with \c{xmlCopyPropList(target, src->properties)}.
+ * The function exits prematurely when any attribute cannot be copied for
+ * ACLs violation. Even without bailing out, the result can possibly be
+ * incosistent with expectations in that case, hence the caller shall,
+ * aposteriori, verify that no document-level-tracked denial was indicated
+ * with \c{xml_acl_denied(target)} and drop whole such intermediate object.
+ *
+ * \param[in,out] target Element to receive attributes from #src element
+ * \param[in] src Element carrying attributes to copy over to #target
+ *
+ * \note Original commit 1c632c506 sadly haven't stated which otherwise
+ * assumed behaviours of xmlCopyPropList were missing beyond otherwise
+ * custom extensions like said ACLs and "atomic increment" (that landed
+ * later on, anyway).
+ */
+void copy_in_properties(xmlNode *target, const xmlNode *src);
+
+void expand_plus_plus(xmlNode * target, const char *name, const char *value);
+void fix_plus_plus_recursive(xmlNode * target);
+
+/*
+ * Create a node named "name" as a child of "parent"
+ * If parent is NULL, creates an unconnected node.
+ *
+ * Returns the created node
+ *
+ */
+xmlNode *create_xml_node(xmlNode * parent, const char *name);
+
+/*
+ * Create a node named "name" as a child of "parent", giving it the provided
+ * text content.
+ * If parent is NULL, creates an unconnected node.
+ *
+ * Returns the created node
+ *
+ */
+xmlNode *pcmk_create_xml_text_node(xmlNode * parent, const char *name, const char *content);
+
+/*
+ * Create a new HTML node named "element_name" as a child of "parent", giving it the
+ * provided text content. Optionally, apply a CSS #id and #class.
+ *
+ * Returns the created node.
+ */
+xmlNode *pcmk_create_html_node(xmlNode * parent, const char *element_name, const char *id,
+ const char *class_name, const char *text);
+
+/*
+ *
+ */
+void purge_diff_markers(xmlNode * a_node);
+
+/*
+ * Returns a deep copy of src_node
+ *
+ */
+xmlNode *copy_xml(xmlNode * src_node);
+
+/*
+ * Add a copy of xml_node to new_parent
+ */
+xmlNode *add_node_copy(xmlNode * new_parent, xmlNode * xml_node);
+
+/*
+ * XML I/O Functions
+ *
+ * Whitespace between tags is discarded.
+ */
+xmlNode *filename2xml(const char *filename);
+
+xmlNode *stdin2xml(void);
+
+xmlNode *string2xml(const char *input);
+
+int write_xml_fd(xmlNode * xml_node, const char *filename, int fd, gboolean compress);
+int write_xml_file(xmlNode * xml_node, const char *filename, gboolean compress);
+
+char *dump_xml_formatted(xmlNode * msg);
+char *dump_xml_formatted_with_text(xmlNode * msg);
+char *dump_xml_unformatted(xmlNode * msg);
+
+/*
+ * Diff related Functions
+ */
+xmlNode *diff_xml_object(xmlNode * left, xmlNode * right, gboolean suppress);
+
+xmlNode *subtract_xml_object(xmlNode * parent, xmlNode * left, xmlNode * right,
+ gboolean full, gboolean * changed, const char *marker);
+
+gboolean can_prune_leaf(xmlNode * xml_node);
+
+/*
+ * Searching & Modifying
+ */
+xmlNode *find_xml_node(const xmlNode *root, const char *search_path,
+ gboolean must_find);
+
+void xml_remove_prop(xmlNode * obj, const char *name);
+
+gboolean replace_xml_child(xmlNode * parent, xmlNode * child, xmlNode * update,
+ gboolean delete_only);
+
+gboolean update_xml_child(xmlNode * child, xmlNode * to_update);
+
+int find_xml_children(xmlNode ** children, xmlNode * root,
+ const char *tag, const char *field, const char *value,
+ gboolean search_matches);
+
+xmlNode *get_xpath_object(const char *xpath, xmlNode * xml_obj, int error_level);
+xmlNode *get_xpath_object_relative(const char *xpath, xmlNode * xml_obj, int error_level);
+
+static inline const char *
+crm_element_name(const xmlNode *xml)
+{
+ return xml? (const char *)(xml->name) : NULL;
+}
+
+static inline const char *
+crm_map_element_name(const xmlNode *xml)
+{
+ const char *name = crm_element_name(xml);
+
+ if (strcmp(name, "master") == 0) {
+ return "clone";
+ } else {
+ return name;
+ }
+}
+
+gboolean xml_has_children(const xmlNode * root);
+
+char *calculate_on_disk_digest(xmlNode * local_cib);
+char *calculate_operation_digest(xmlNode * local_cib, const char *version);
+char *calculate_xml_versioned_digest(xmlNode * input, gboolean sort, gboolean do_filter,
+ const char *version);
+
+/* schema-related functions (from schemas.c) */
+gboolean validate_xml(xmlNode * xml_blob, const char *validation, gboolean to_logs);
+gboolean validate_xml_verbose(xmlNode * xml_blob);
+
+/*!
+ * \brief Update CIB XML to most recent schema version
+ *
+ * "Update" means either actively employ XSLT-based transformation(s)
+ * (if intermediate product to transform valid per its declared schema version,
+ * transformation available, proceeded successfully with a result valid per
+ * expectated newer schema version), or just try to bump the marked validating
+ * schema until all gradually rising schema versions attested or the first
+ * such attempt subsequently fails to validate. Which of the two styles will
+ * be used depends on \p transform parameter (positive/negative, respectively).
+ *
+ * \param[in,out] xml_blob XML tree representing CIB, may be swapped with
+ * an "updated" one
+ * \param[out] best The highest configuration version (per its index
+ * in the global schemas table) it was possible to
+ * reach during the update steps while ensuring
+ * the validity of the result; if no validation
+ * success was observed against possibly multiple
+ * schemas, the value is less or equal the result
+ * of \c get_schema_version applied on the input
+ * \p xml_blob value (unless that function maps it
+ * to -1, then 0 would be used instead)
+ * \param[in] max When \p transform is positive, this allows to
+ * set upper boundary schema (per its index in the
+ * global schemas table) beyond which it's forbidden
+ * to update by the means of XSLT transformation
+ * \param[in] transform Whether to employ XSLT-based transformation so
+ * as to allow overcoming possible incompatibilities
+ * between major schema versions (see above)
+ * \param[in] to_logs If true, output notable progress info to
+ * internal log streams; if false, to stderr
+ *
+ * \return \c pcmk_ok if no non-recoverable error encountered (up to
+ * caller to evaluate if the update satisfies the requirements
+ * per returned \p best value), negative value carrying the reason
+ * otherwise
+ */
+int update_validation(xmlNode **xml_blob, int *best, int max,
+ gboolean transform, gboolean to_logs);
+
+int get_schema_version(const char *name);
+const char *get_schema_name(int version);
+const char *xml_latest_schema(void);
+gboolean cli_config_update(xmlNode ** xml, int *best_version, gboolean to_logs);
+
+/*!
+ * \brief Initialize the CRM XML subsystem
+ *
+ * This method sets global XML settings and loads pacemaker schemas into the cache.
+ */
+void crm_xml_init(void);
+void crm_xml_cleanup(void);
+
+void pcmk_free_xml_subtree(xmlNode *xml);
+void free_xml(xmlNode * child);
+
+xmlNode *first_named_child(const xmlNode *parent, const char *name);
+xmlNode *crm_next_same_xml(const xmlNode *sibling);
+
+xmlNode *sorted_xml(xmlNode * input, xmlNode * parent, gboolean recursive);
+xmlXPathObjectPtr xpath_search(xmlNode * xml_top, const char *path);
+void crm_foreach_xpath_result(xmlNode *xml, const char *xpath,
+ void (*helper)(xmlNode*, void*), void *user_data);
+xmlNode *expand_idref(xmlNode * input, xmlNode * top);
+
+void freeXpathObject(xmlXPathObjectPtr xpathObj);
+xmlNode *getXpathResult(xmlXPathObjectPtr xpathObj, int index);
+void dedupXpathResults(xmlXPathObjectPtr xpathObj);
+
+static inline int numXpathResults(xmlXPathObjectPtr xpathObj)
+{
+ if(xpathObj == NULL || xpathObj->nodesetval == NULL) {
+ return 0;
+ }
+ return xpathObj->nodesetval->nodeNr;
+}
+
+bool xml_tracking_changes(xmlNode * xml);
+bool xml_document_dirty(xmlNode *xml);
+void xml_track_changes(xmlNode * xml, const char *user, xmlNode *acl_source, bool enforce_acls);
+void xml_calculate_changes(xmlNode *old_xml, xmlNode *new_xml);
+void xml_calculate_significant_changes(xmlNode *old_xml, xmlNode *new_xml);
+void xml_accept_changes(xmlNode * xml);
+bool xml_patch_versions(const xmlNode *patchset, int add[3], int del[3]);
+
+xmlNode *xml_create_patchset(
+ int format, xmlNode *source, xmlNode *target, bool *config, bool manage_version);
+int xml_apply_patchset(xmlNode *xml, xmlNode *patchset, bool check_version);
+
+void patchset_process_digest(xmlNode *patch, xmlNode *source, xmlNode *target, bool with_digest);
+
+void save_xml_to_file(xmlNode * xml, const char *desc, const char *filename);
+
+char * crm_xml_escape(const char *text);
+void crm_xml_sanitize_id(char *id);
+void crm_xml_set_id(xmlNode *xml, const char *format, ...) G_GNUC_PRINTF(2, 3);
+
+#if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1)
+#include <crm/common/xml_compat.h>
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/crm/common/xml_compat.h b/include/crm/common/xml_compat.h
new file mode 100644
index 0000000..bb49b68
--- /dev/null
+++ b/include/crm/common/xml_compat.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2004-2023 the Pacemaker project contributors
+ *
+ * The version control history for this file may have further details.
+ *
+ * This source code is licensed under the GNU Lesser General Public License
+ * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
+ */
+
+#ifndef PCMK__CRM_COMMON_XML_COMPAT__H
+# define PCMK__CRM_COMMON_XML_COMPAT__H
+
+#include <glib.h> // gboolean
+#include <libxml/tree.h> // xmlNode
+#include <crm/common/xml.h> // crm_xml_add()
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * \file
+ * \brief Deprecated Pacemaker XML API
+ * \ingroup core
+ * \deprecated Do not include this header directly. The XML APIs in this
+ * header, and the header itself, will be removed in a future
+ * release.
+ */
+
+//! \deprecated Do not use (will be removed in a future release)
+#define XML_PARANOIA_CHECKS 0
+
+//! \deprecated This function will be removed in a future release
+int add_node_nocopy(xmlNode * parent, const char *name, xmlNode * child);
+
+//! \deprecated This function will be removed in a future release
+xmlNode *find_entity(xmlNode *parent, const char *node_name, const char *id);
+
+//! \deprecated This function will be removed in a future release
+char *xml_get_path(const xmlNode *xml);
+
+//! \deprecated This function will be removed in a future release
+void xml_log_changes(uint8_t level, const char *function, const xmlNode *xml);
+
+//! \deprecated This function will be removed in a future release
+void xml_log_patchset(uint8_t level, const char *function, const xmlNode *xml);
+
+//! \deprecated Use xml_apply_patchset() instead
+gboolean apply_xml_diff(xmlNode *old_xml, xmlNode *diff, xmlNode **new_xml);
+
+//! \deprecated Do not use (will be removed in a future release)
+void crm_destroy_xml(gpointer data);
+
+//! \deprecated Use crm_xml_add() with "true" or "false" instead
+static inline const char *
+crm_xml_add_boolean(xmlNode *node, const char *name, gboolean value)
+{
+ return crm_xml_add(node, name, (value? "true" : "false"));
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // PCMK__CRM_COMMON_XML_COMPAT__H
diff --git a/include/crm/common/xml_internal.h b/include/crm/common/xml_internal.h
new file mode 100644
index 0000000..43b3b8c
--- /dev/null
+++ b/include/crm/common/xml_internal.h
@@ -0,0 +1,414 @@
+/*
+ * Copyright 2017-2023 the Pacemaker project contributors
+ *
+ * The version control history for this file may have further details.
+ *
+ * This source code is licensed under the GNU Lesser General Public License
+ * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
+ */
+
+#ifndef PCMK__XML_INTERNAL__H
+# define PCMK__XML_INTERNAL__H
+
+/*
+ * Internal-only wrappers for and extensions to libxml2 (libxslt)
+ */
+
+# include <stdlib.h>
+# include <stdio.h>
+# include <string.h>
+
+# include <crm/crm.h> /* transitively imports qblog.h */
+# include <crm/common/output_internal.h>
+
+
+/*!
+ * \brief Base for directing lib{xml2,xslt} log into standard libqb backend
+ *
+ * This macro implements the core of what can be needed for directing
+ * libxml2 or libxslt error messaging into standard, preconfigured
+ * libqb-backed log stream.
+ *
+ * It's a bit unfortunate that libxml2 (and more sparsely, also libxslt)
+ * emits a single message by chunks (location is emitted separatedly from
+ * the message itself), so we have to take the effort to combine these
+ * chunks back to single message. Whether to do this or not is driven
+ * with \p dechunk toggle.
+ *
+ * The form of a macro was chosen for implicit deriving of __FILE__, etc.
+ * and also because static dechunking buffer should be differentiated per
+ * library (here we assume different functions referring to this macro
+ * will not ever be using both at once), preferably also per-library
+ * context of use to avoid clashes altogether.
+ *
+ * Note that we cannot use qb_logt, because callsite data have to be known
+ * at the moment of compilation, which it is not always the case -- xml_log
+ * (and unfortunately there's no clear explanation of the fail to compile).
+ *
+ * Also note that there's no explicit guard against said libraries producing
+ * never-newline-terminated chunks (which would just keep consuming memory),
+ * as it's quite improbable. Termination of the program in between the
+ * same-message chunks will raise a flag with valgrind and the likes, though.
+ *
+ * And lastly, regarding how dechunking combines with other non-message
+ * parameters -- for \p priority, most important running specification
+ * wins (possibly elevated to LOG_ERR in case of nonconformance with the
+ * newline-termination "protocol"), \p dechunk is expected to always be
+ * on once it was at the start, and the rest (\p postemit and \p prefix)
+ * are picked directly from the last chunk entry finalizing the message
+ * (also reasonable to always have it the same with all related entries).
+ *
+ * \param[in] priority Syslog priority for the message to be logged
+ * \param[in] dechunk Whether to dechunk new-line terminated message
+ * \param[in] postemit Code to be executed once message is sent out
+ * \param[in] prefix How to prefix the message or NULL for raw passing
+ * \param[in] fmt Format string as with printf-like functions
+ * \param[in] ap Variable argument list to supplement \p fmt format string
+ */
+#define PCMK__XML_LOG_BASE(priority, dechunk, postemit, prefix, fmt, ap) \
+do { \
+ if (!(dechunk) && (prefix) == NULL) { /* quick pass */ \
+ qb_log_from_external_source_va(__func__, __FILE__, (fmt), \
+ (priority), __LINE__, 0, (ap)); \
+ (void) (postemit); \
+ } else { \
+ int CXLB_len = 0; \
+ char *CXLB_buf = NULL; \
+ static int CXLB_buffer_len = 0; \
+ static char *CXLB_buffer = NULL; \
+ static uint8_t CXLB_priority = 0; \
+ \
+ CXLB_len = vasprintf(&CXLB_buf, (fmt), (ap)); \
+ \
+ if (CXLB_len <= 0 || CXLB_buf[CXLB_len - 1] == '\n' || !(dechunk)) { \
+ if (CXLB_len < 0) { \
+ CXLB_buf = (char *) "LOG CORRUPTION HAZARD"; /*we don't modify*/\
+ CXLB_priority = QB_MIN(CXLB_priority, LOG_ERR); \
+ } else if (CXLB_len > 0 /* && (dechunk) */ \
+ && CXLB_buf[CXLB_len - 1] == '\n') { \
+ CXLB_buf[CXLB_len - 1] = '\0'; \
+ } \
+ if (CXLB_buffer) { \
+ qb_log_from_external_source(__func__, __FILE__, "%s%s%s", \
+ CXLB_priority, __LINE__, 0, \
+ (prefix) != NULL ? (prefix) : "", \
+ CXLB_buffer, CXLB_buf); \
+ free(CXLB_buffer); \
+ } else { \
+ qb_log_from_external_source(__func__, __FILE__, "%s%s", \
+ (priority), __LINE__, 0, \
+ (prefix) != NULL ? (prefix) : "", \
+ CXLB_buf); \
+ } \
+ if (CXLB_len < 0) { \
+ CXLB_buf = NULL; /* restore temporary override */ \
+ } \
+ CXLB_buffer = NULL; \
+ CXLB_buffer_len = 0; \
+ (void) (postemit); \
+ \
+ } else if (CXLB_buffer == NULL) { \
+ CXLB_buffer_len = CXLB_len; \
+ CXLB_buffer = CXLB_buf; \
+ CXLB_buf = NULL; \
+ CXLB_priority = (priority); /* remember as a running severest */ \
+ \
+ } else { \
+ CXLB_buffer = realloc(CXLB_buffer, 1 + CXLB_buffer_len + CXLB_len); \
+ memcpy(CXLB_buffer + CXLB_buffer_len, CXLB_buf, CXLB_len); \
+ CXLB_buffer_len += CXLB_len; \
+ CXLB_buffer[CXLB_buffer_len] = '\0'; \
+ CXLB_priority = QB_MIN(CXLB_priority, (priority)); /* severest? */ \
+ } \
+ free(CXLB_buf); \
+ } \
+} while (0)
+
+/*
+ * \enum pcmk__xml_fmt_options
+ * \brief Bit flags to control format in XML logs and dumps
+ */
+enum pcmk__xml_fmt_options {
+ //! Exclude certain XML attributes (for calculating digests)
+ pcmk__xml_fmt_filtered = (1 << 0),
+
+ //! Include indentation and newlines
+ pcmk__xml_fmt_pretty = (1 << 1),
+
+ //! Include full XML subtree (with any text), using libxml serialization
+ pcmk__xml_fmt_full = (1 << 2),
+
+ //! Include the opening tag of an XML element, and include XML comments
+ pcmk__xml_fmt_open = (1 << 3),
+
+ //! Include the children of an XML element
+ pcmk__xml_fmt_children = (1 << 4),
+
+ //! Include the closing tag of an XML element
+ pcmk__xml_fmt_close = (1 << 5),
+
+ // @COMPAT Remove when log_data_element() is removed
+ //! Include XML text nodes
+ pcmk__xml_fmt_text = (1 << 6),
+
+ // @COMPAT Remove when v1 patchsets are removed
+ //! Log a created XML subtree
+ pcmk__xml_fmt_diff_plus = (1 << 7),
+
+ // @COMPAT Remove when v1 patchsets are removed
+ //! Log a removed XML subtree
+ pcmk__xml_fmt_diff_minus = (1 << 8),
+
+ // @COMPAT Remove when v1 patchsets are removed
+ //! Log a minimal version of an XML diff (only showing the changes)
+ pcmk__xml_fmt_diff_short = (1 << 9),
+};
+
+int pcmk__xml_show(pcmk__output_t *out, const char *prefix, const xmlNode *data,
+ int depth, uint32_t options);
+int pcmk__xml_show_changes(pcmk__output_t *out, const xmlNode *xml);
+
+/* XML search strings for guest, remote and pacemaker_remote nodes */
+
+/* search string to find CIB resources entries for cluster nodes */
+#define PCMK__XP_MEMBER_NODE_CONFIG \
+ "//" XML_TAG_CIB "/" XML_CIB_TAG_CONFIGURATION "/" XML_CIB_TAG_NODES \
+ "/" XML_CIB_TAG_NODE "[not(@type) or @type='member']"
+
+/* search string to find CIB resources entries for guest nodes */
+#define PCMK__XP_GUEST_NODE_CONFIG \
+ "//" XML_TAG_CIB "//" XML_CIB_TAG_CONFIGURATION "//" XML_CIB_TAG_RESOURCE \
+ "//" XML_TAG_META_SETS "//" XML_CIB_TAG_NVPAIR \
+ "[@name='" XML_RSC_ATTR_REMOTE_NODE "']"
+
+/* search string to find CIB resources entries for remote nodes */
+#define PCMK__XP_REMOTE_NODE_CONFIG \
+ "//" XML_TAG_CIB "//" XML_CIB_TAG_CONFIGURATION "//" XML_CIB_TAG_RESOURCE \
+ "[@type='remote'][@provider='pacemaker']"
+
+/* search string to find CIB node status entries for pacemaker_remote nodes */
+#define PCMK__XP_REMOTE_NODE_STATUS \
+ "//" XML_TAG_CIB "//" XML_CIB_TAG_STATUS "//" XML_CIB_TAG_STATE \
+ "[@" XML_NODE_IS_REMOTE "='true']"
+
+enum pcmk__xml_artefact_ns {
+ pcmk__xml_artefact_ns_legacy_rng = 1,
+ pcmk__xml_artefact_ns_legacy_xslt,
+ pcmk__xml_artefact_ns_base_rng,
+ pcmk__xml_artefact_ns_base_xslt,
+};
+
+void pcmk__strip_xml_text(xmlNode *xml);
+const char *pcmk__xe_add_last_written(xmlNode *xe);
+
+xmlNode *pcmk__xe_match(const xmlNode *parent, const char *node_name,
+ const char *attr_n, const char *attr_v);
+
+void pcmk__xe_remove_matching_attrs(xmlNode *element,
+ bool (*match)(xmlAttrPtr, void *),
+ void *user_data);
+
+GString *pcmk__element_xpath(const xmlNode *xml);
+
+/*!
+ * \internal
+ * \brief Get the root directory to scan XML artefacts of given kind for
+ *
+ * \param[in] ns governs the hierarchy nesting against the inherent root dir
+ *
+ * \return root directory to scan XML artefacts of given kind for
+ */
+char *
+pcmk__xml_artefact_root(enum pcmk__xml_artefact_ns ns);
+
+/*!
+ * \internal
+ * \brief Get the fully unwrapped path to particular XML artifact (RNG/XSLT)
+ *
+ * \param[in] ns denotes path forming details (parent dir, suffix)
+ * \param[in] filespec symbolic file specification to be combined with
+ * #artefact_ns to form the final path
+ * \return unwrapped path to particular XML artifact (RNG/XSLT)
+ */
+char *pcmk__xml_artefact_path(enum pcmk__xml_artefact_ns ns,
+ const char *filespec);
+
+/*!
+ * \internal
+ * \brief Return first non-text child node of an XML node
+ *
+ * \param[in] parent XML node to check
+ *
+ * \return First non-text child node of \p parent (or NULL if none)
+ */
+static inline xmlNode *
+pcmk__xml_first_child(const xmlNode *parent)
+{
+ xmlNode *child = (parent? parent->children : NULL);
+
+ while (child && (child->type == XML_TEXT_NODE)) {
+ child = child->next;
+ }
+ return child;
+}
+
+/*!
+ * \internal
+ * \brief Return next non-text sibling node of an XML node
+ *
+ * \param[in] child XML node to check
+ *
+ * \return Next non-text sibling of \p child (or NULL if none)
+ */
+static inline xmlNode *
+pcmk__xml_next(const xmlNode *child)
+{
+ xmlNode *next = (child? child->next : NULL);
+
+ while (next && (next->type == XML_TEXT_NODE)) {
+ next = next->next;
+ }
+ return next;
+}
+
+/*!
+ * \internal
+ * \brief Return first non-text child element of an XML node
+ *
+ * \param[in] parent XML node to check
+ *
+ * \return First child element of \p parent (or NULL if none)
+ */
+static inline xmlNode *
+pcmk__xe_first_child(const xmlNode *parent)
+{
+ xmlNode *child = (parent? parent->children : NULL);
+
+ while (child && (child->type != XML_ELEMENT_NODE)) {
+ child = child->next;
+ }
+ return child;
+}
+
+/*!
+ * \internal
+ * \brief Return next non-text sibling element of an XML element
+ *
+ * \param[in] child XML element to check
+ *
+ * \return Next sibling element of \p child (or NULL if none)
+ */
+static inline xmlNode *
+pcmk__xe_next(const xmlNode *child)
+{
+ xmlNode *next = child? child->next : NULL;
+
+ while (next && (next->type != XML_ELEMENT_NODE)) {
+ next = next->next;
+ }
+ return next;
+}
+
+/*!
+ * \internal
+ * \brief Like pcmk__xe_set_props, but takes a va_list instead of
+ * arguments directly.
+ *
+ * \param[in,out] node XML to add attributes to
+ * \param[in] pairs NULL-terminated list of name/value pairs to add
+ */
+void
+pcmk__xe_set_propv(xmlNodePtr node, va_list pairs);
+
+/*!
+ * \internal
+ * \brief Add a NULL-terminated list of name/value pairs to the given
+ * XML node as properties.
+ *
+ * \param[in,out] node XML node to add properties to
+ * \param[in] ... NULL-terminated list of name/value pairs
+ *
+ * \note A NULL name terminates the arguments; a NULL value will be skipped.
+ */
+void
+pcmk__xe_set_props(xmlNodePtr node, ...)
+G_GNUC_NULL_TERMINATED;
+
+/*!
+ * \internal
+ * \brief Get first attribute of an XML element
+ *
+ * \param[in] xe XML element to check
+ *
+ * \return First attribute of \p xe (or NULL if \p xe is NULL or has none)
+ */
+static inline xmlAttr *
+pcmk__xe_first_attr(const xmlNode *xe)
+{
+ return (xe == NULL)? NULL : xe->properties;
+}
+
+/*!
+ * \internal
+ * \brief Extract the ID attribute from an XML element
+ *
+ * \param[in] xpath String to search
+ * \param[in] node Node to get the ID for
+ *
+ * \return ID attribute of \p node in xpath string \p xpath
+ */
+char *
+pcmk__xpath_node_id(const char *xpath, const char *node);
+
+/* internal XML-related utilities */
+
+enum xml_private_flags {
+ pcmk__xf_none = 0x0000,
+ pcmk__xf_dirty = 0x0001,
+ pcmk__xf_deleted = 0x0002,
+ pcmk__xf_created = 0x0004,
+ pcmk__xf_modified = 0x0008,
+
+ pcmk__xf_tracking = 0x0010,
+ pcmk__xf_processed = 0x0020,
+ pcmk__xf_skip = 0x0040,
+ pcmk__xf_moved = 0x0080,
+
+ pcmk__xf_acl_enabled = 0x0100,
+ pcmk__xf_acl_read = 0x0200,
+ pcmk__xf_acl_write = 0x0400,
+ pcmk__xf_acl_deny = 0x0800,
+
+ pcmk__xf_acl_create = 0x1000,
+ pcmk__xf_acl_denied = 0x2000,
+ pcmk__xf_lazy = 0x4000,
+};
+
+void pcmk__set_xml_doc_flag(xmlNode *xml, enum xml_private_flags flag);
+
+/*!
+ * \internal
+ * \brief Iterate over child elements of \p xml
+ *
+ * This function iterates over the children of \p xml, performing the
+ * callback function \p handler on each node. If the callback returns
+ * a value other than pcmk_rc_ok, the iteration stops and the value is
+ * returned. It is therefore possible that not all children will be
+ * visited.
+ *
+ * \param[in,out] xml The starting XML node. Can be NULL.
+ * \param[in] child_element_name The name that the node must match in order
+ * for \p handler to be run. If NULL, all
+ * child elements will match.
+ * \param[in] handler The callback function.
+ * \param[in,out] userdata User data to pass to the callback function.
+ * Can be NULL.
+ *
+ * \return Standard Pacemaker return code
+ */
+int
+pcmk__xe_foreach_child(xmlNode *xml, const char *child_element_name,
+ int (*handler)(xmlNode *xml, void *userdata),
+ void *userdata);
+
+#endif // PCMK__XML_INTERNAL__H
diff --git a/include/crm/compatibility.h b/include/crm/compatibility.h
new file mode 100644
index 0000000..1281a3c
--- /dev/null
+++ b/include/crm/compatibility.h
@@ -0,0 +1,243 @@
+/*
+ * Copyright 2004-2021 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.
+ */
+#ifndef PCMK__CRM_COMPATIBILITY__H
+# define PCMK__CRM_COMPATIBILITY__H
+
+#include <crm/msg_xml.h>
+#include <crm/pengine/pe_types.h> // enum pe_obj_types
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* This file allows external code that uses Pacemaker libraries to transition
+ * more easily from old APIs to current ones. Any code that compiled with an
+ * earlier API but not with the current API can include this file and have a
+ * good chance of compiling again.
+ *
+ * Everything here is deprecated and will be removed at the next major Pacemaker
+ * release (i.e. 3.0), so it should only be used during a transitionary period
+ * while the external code is being updated to the current API.
+ */
+
+/* Heartbeat-specific definitions. Support for heartbeat has been removed
+ * entirely, so any code branches relying on these should be deleted.
+ */
+#define ACTIVESTATUS "active"
+#define DEADSTATUS "dead"
+#define PINGSTATUS "ping"
+#define JOINSTATUS "join"
+#define LEAVESTATUS "leave"
+#define NORMALNODE "normal"
+#define CRM_NODE_EVICTED "evicted"
+#define CRM_LEGACY_CONFIG_DIR "/var/lib/heartbeat/crm"
+#define HA_VARLIBHBDIR "/var/lib/heartbeat"
+#define pcmk_cluster_heartbeat 0x0004
+
+/* Corosync-version-1-specific definitions */
+
+/* Support for corosync version 1 has been removed entirely, so any code
+ * branches relying on these should be deleted.
+ */
+#define PCMK_SERVICE_ID 9
+#define CRM_SERVICE PCMK_SERVICE_ID
+#define XML_ATTR_EXPECTED_VOTES "expected-quorum-votes"
+#define crm_class_members 1
+#define crm_class_notify 2
+#define crm_class_nodeid 3
+#define crm_class_rmpeer 4
+#define crm_class_quorum 5
+#define pcmk_cluster_classic_ais 0x0010
+#define pcmk_cluster_cman 0x0040
+#define ais_fd_sync -1
+
+// These are always true now
+#define CS_USES_LIBQB 1
+#define HAVE_CMAP 1
+#define SUPPORT_CS_QUORUM 1
+#define SUPPORT_AIS 1
+#define AIS_COROSYNC 1
+
+// These are always false now
+#define HAVE_CONFDB 0
+#define SUPPORT_CMAN 0
+#define SUPPORT_PLUGIN 0
+#define SUPPORT_STONITH_CONFIG 0
+#define is_classic_ais_cluster() 0
+#define is_cman_cluster() 0
+
+// These have newer names
+#define is_openais_cluster() is_corosync_cluster()
+#if SUPPORT_COROSYNC
+#define SUPPORT_CS
+#endif
+
+/* Isolation-specific definitions. Support for the resource isolation feature
+ * has been removed * entirely, so any code branches relying on these should be
+ * deleted.
+ */
+#define XML_RSC_ATTR_ISOLATION_INSTANCE "isolation-instance"
+#define XML_RSC_ATTR_ISOLATION_WRAPPER "isolation-wrapper"
+#define XML_RSC_ATTR_ISOLATION_HOST "isolation-host"
+#define XML_RSC_ATTR_ISOLATION "isolation"
+
+/* Schema-related definitions */
+
+// This has been renamed
+#define CRM_DTD_DIRECTORY CRM_SCHEMA_DIRECTORY
+
+/* Exit-code-related definitions */
+
+#define DAEMON_RESPAWN_STOP CRM_EX_FATAL
+#define pcmk_err_panic CRM_EX_PANIC
+
+// Deprecated symbols that were removed
+#define APPNAME_LEN 256
+#define CRM_NODE_ACTIVE CRM_NODE_MEMBER
+#define CRM_OP_DIE "die_no_respawn"
+#define CRM_OP_RETRIVE_CIB "retrieve_cib"
+#define CRM_OP_HBEAT "dc_beat"
+#define CRM_OP_ABORT "abort"
+#define CRM_OP_DEBUG_UP "debug_inc"
+#define CRM_OP_DEBUG_DOWN "debug_dec"
+#define CRM_OP_EVENTCC "event_cc"
+#define CRM_OP_TEABORT "te_abort"
+#define CRM_OP_TEABORTED "te_abort_confirmed"
+#define CRM_OP_TE_HALT "te_halt"
+#define CRM_OP_TECOMPLETE "te_complete"
+#define CRM_OP_TETIMEOUT "te_timeout"
+#define CRM_OP_TRANSITION "transition"
+#define CRM_OP_NODES_PROBED "probe_nodes_complete"
+#define DOT_ALL_FSA_INPUTS 1
+#define DOT_FSA_ACTIONS 1
+#define F_LRMD_CANCEL_CALLID "lrmd_cancel_callid"
+#define F_LRMD_RSC_METADATA "lrmd_rsc_metadata_res"
+#define F_LRMD_IPC_PROXY_NODE "lrmd_ipc_proxy_node"
+#define INSTANCE(x) crm_element_value(x, XML_CIB_ATTR_INSTANCE)
+#define LOG_DEBUG_2 LOG_TRACE
+#define LOG_DEBUG_3 LOG_TRACE
+#define LOG_DEBUG_4 LOG_TRACE
+#define LOG_DEBUG_5 LOG_TRACE
+#define LOG_DEBUG_6 LOG_TRACE
+#define LRMD_OP_RSC_CHK_REG "lrmd_rsc_check_register"
+#define MAX_IPC_FAIL 5
+#define NAME(x) crm_element_value(x, XML_NVPAIR_ATTR_NAME)
+#define MSG_LOG 1
+#define PE_OBJ_T_NATIVE "native"
+#define PE_OBJ_T_GROUP "group"
+#define PE_OBJ_T_INCARNATION "clone"
+#define PE_OBJ_T_MASTER "master"
+#define SERVICE_SCRIPT "/sbin/service"
+#define SOCKET_LEN 1024
+#define TSTAMP(x) crm_element_value(x, XML_ATTR_TSTAMP)
+#define XML_ATTR_TAGNAME F_XML_TAGNAME
+#define XML_ATTR_FILTER_TYPE "type-filter"
+#define XML_ATTR_FILTER_ID "id-filter"
+#define XML_ATTR_FILTER_PRIORITY "priority-filter"
+#define XML_ATTR_DC "is_dc"
+#define XML_MSG_TAG "crm_message"
+#define XML_MSG_TAG_DATA "msg_data"
+#define XML_FAIL_TAG_RESOURCE "failed_resource"
+#define XML_FAILRES_ATTR_RESID "resource_id"
+#define XML_FAILRES_ATTR_REASON "reason"
+#define XML_FAILRES_ATTR_RESSTATUS "resource_status"
+#define XML_ATTR_RESULT "result"
+#define XML_ATTR_SECTION "section"
+#define XML_CIB_TAG_DOMAIN "domain"
+#define XML_CIB_TAG_CONSTRAINT "constraint"
+#define XML_RSC_ATTR_STATE "clone-state"
+#define XML_RSC_ATTR_PRIORITY "priority"
+#define XML_OP_ATTR_DEPENDENT "dependent-on"
+#define XML_LRM_TAG_AGENTS "lrm_agents"
+#define XML_LRM_TAG_AGENT "lrm_agent"
+#define XML_LRM_TAG_ATTRIBUTES "attributes"
+#define XML_CIB_ATTR_HEALTH "health"
+#define XML_CIB_ATTR_WEIGHT "weight"
+#define XML_CIB_ATTR_CLEAR "clear_on"
+#define XML_CIB_ATTR_STONITH "stonith"
+#define XML_CIB_ATTR_STANDBY "standby"
+#define XML_RULE_ATTR_SCORE_MANGLED "score-attribute-mangled"
+#define XML_RULE_ATTR_RESULT "result"
+#define XML_NODE_ATTR_STATE "state"
+#define XML_ATTR_LRM_PROBE "lrm-is-probe"
+#define XML_ATTR_TE_ALLOWFAIL "op_allow_fail"
+#define VALUE(x) crm_element_value(x, XML_NVPAIR_ATTR_VALUE)
+#define action_wrapper_s pe_action_wrapper_s
+#define add_cib_op_callback(cib, id, flag, data, fn) do { \
+ cib->cmds->register_callback(cib, id, 120, flag, data, #fn, fn); \
+ } while(0)
+#define cib_default_options = cib_none
+#define crm_remote_baremetal 0x0004
+#define crm_remote_container 0x0002
+#define crm_element_value_const crm_element_value
+#define crm_element_value_const_int crm_element_value_int
+#define n_object_classes 3
+#define no_quorum_policy_e pe_quorum_policy
+#define node_s pe_node_s
+#define node_shared_s pe_node_shared_s
+#define pe_action_failure_is_fatal 0x00020
+#define pe_rsc_munging 0x00000800ULL
+#define pe_rsc_try_reload 0x00001000ULL
+#define pe_rsc_shutdown 0x00020000ULL
+#define pe_rsc_migrating 0x00400000ULL
+#define pe_rsc_unexpectedly_running 0x02000000ULL
+#define pe_rsc_have_unfencing 0x80000000ULL
+#define resource_s pe_resource_s
+#define ticket_s pe_ticket_s
+
+#define node_score_infinity 1000000
+
+/* Clone terminology definitions */
+
+// These can no longer be used in a switch together
+#define pe_master pe_clone
+
+static inline enum pe_obj_types
+get_resource_type(const char *name)
+{
+ if (safe_str_eq(name, XML_CIB_TAG_RESOURCE)) {
+ return pe_native;
+
+ } else if (safe_str_eq(name, XML_CIB_TAG_GROUP)) {
+ return pe_group;
+
+ } else if (safe_str_eq(name, XML_CIB_TAG_INCARNATION)
+ || safe_str_eq(name, PCMK_XE_PROMOTABLE_LEGACY)) {
+ return pe_clone;
+
+ } else if (safe_str_eq(name, XML_CIB_TAG_CONTAINER)) {
+ return pe_container;
+ }
+
+ return pe_unknown;
+}
+
+static inline const char *
+get_resource_typename(enum pe_obj_types type)
+{
+ switch (type) {
+ case pe_native:
+ return XML_CIB_TAG_RESOURCE;
+ case pe_group:
+ return XML_CIB_TAG_GROUP;
+ case pe_clone:
+ return XML_CIB_TAG_INCARNATION;
+ case pe_container:
+ return XML_CIB_TAG_CONTAINER;
+ case pe_unknown:
+ return "unknown";
+ }
+ return "<unknown>";
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/crm/crm.h b/include/crm/crm.h
new file mode 100644
index 0000000..e824825
--- /dev/null
+++ b/include/crm/crm.h
@@ -0,0 +1,238 @@
+/*
+ * Copyright 2004-2023 the Pacemaker project contributors
+ *
+ * The version control history for this file may have further details.
+ *
+ * This source code is licensed under the GNU Lesser General Public License
+ * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
+ */
+
+#ifndef PCMK__CRM_CRM__H
+# define PCMK__CRM_CRM__H
+
+# include <crm_config.h>
+# include <stdlib.h>
+# include <glib.h>
+# include <stdbool.h>
+
+# include <string.h>
+
+# include <libxml/tree.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * \file
+ * \brief A dumping ground
+ * \ingroup core
+ */
+
+#ifndef PCMK_ALLOW_DEPRECATED
+/*!
+ * \brief Allow use of deprecated Pacemaker APIs
+ *
+ * By default, external code using Pacemaker headers is allowed to use
+ * deprecated Pacemaker APIs. If PCMK_ALLOW_DEPRECATED is defined to 0 before
+ * including any Pacemaker headers, deprecated APIs will be unusable. It is
+ * strongly recommended to leave this unchanged for production and release
+ * builds, to avoid breakage when users upgrade to new Pacemaker releases that
+ * deprecate more APIs. This should be defined to 0 only for development and
+ * testing builds when desiring to check for usage of currently deprecated APIs.
+ */
+#define PCMK_ALLOW_DEPRECATED 1
+#endif
+
+/*!
+ * The CRM feature set assists with compatibility in mixed-version clusters.
+ * The major version number increases when nodes with different versions
+ * would not work (rolling upgrades are not allowed). The minor version
+ * number increases when mixed-version clusters are allowed only during
+ * rolling upgrades (a node with the oldest feature set will be elected DC). The
+ * minor-minor version number is ignored, but allows resource agents to detect
+ * cluster support for various features.
+ *
+ * The feature set also affects the processing of old saved CIBs (such as for
+ * many scheduler regression tests).
+ *
+ * Particular feature points currently tested by Pacemaker code:
+ *
+ * >2.1: Operation updates include timing data
+ * >=3.0.5: XML v2 digests are created
+ * >=3.0.8: Peers do not need acks for cancellations
+ * >=3.0.9: DC will send its own shutdown request to all peers
+ * XML v2 patchsets are created by default
+ * >=3.0.13: Fail counts include operation name and interval
+ * >=3.2.0: DC supports PCMK_EXEC_INVALID and PCMK_EXEC_NOT_CONNECTED
+ */
+# define CRM_FEATURE_SET "3.17.4"
+
+/* Pacemaker's CPG protocols use fixed-width binary fields for the sender and
+ * recipient of a CPG message. This imposes an arbitrary limit on cluster node
+ * names.
+ */
+//! \brief Maximum length of a Corosync cluster node name (in bytes)
+#define MAX_NAME 256
+
+# define CRM_META "CRM_meta"
+
+extern char *crm_system_name;
+
+/* *INDENT-OFF* */
+
+// How we represent "infinite" scores
+# define CRM_SCORE_INFINITY 1000000
+# define CRM_INFINITY_S "INFINITY"
+# define CRM_PLUS_INFINITY_S "+" CRM_INFINITY_S
+# define CRM_MINUS_INFINITY_S "-" CRM_INFINITY_S
+
+/* @COMPAT API < 2.0.0 Deprecated "infinity" aliases
+ *
+ * INFINITY might be defined elsewhere (e.g. math.h), so undefine it first.
+ * This, of course, complicates any attempt to use the other definition in any
+ * code that includes this header.
+ */
+# undef INFINITY
+# define INFINITY_S "INFINITY"
+# define MINUS_INFINITY_S "-INFINITY"
+# define INFINITY 1000000
+
+/* Sub-systems */
+# define CRM_SYSTEM_DC "dc"
+#define CRM_SYSTEM_DCIB "dcib" // Primary instance of CIB manager
+# define CRM_SYSTEM_CIB "cib"
+# define CRM_SYSTEM_CRMD "crmd"
+# define CRM_SYSTEM_LRMD "lrmd"
+# define CRM_SYSTEM_PENGINE "pengine"
+# define CRM_SYSTEM_TENGINE "tengine"
+# define CRM_SYSTEM_STONITHD "stonithd"
+# define CRM_SYSTEM_MCP "pacemakerd"
+
+// Names of internally generated node attributes
+# define CRM_ATTR_UNAME "#uname"
+# define CRM_ATTR_ID "#id"
+# define CRM_ATTR_KIND "#kind"
+# define CRM_ATTR_ROLE "#role"
+# define CRM_ATTR_IS_DC "#is_dc"
+# define CRM_ATTR_CLUSTER_NAME "#cluster-name"
+# define CRM_ATTR_SITE_NAME "#site-name"
+# define CRM_ATTR_UNFENCED "#node-unfenced"
+# define CRM_ATTR_DIGESTS_ALL "#digests-all"
+# define CRM_ATTR_DIGESTS_SECURE "#digests-secure"
+# define CRM_ATTR_PROTOCOL "#attrd-protocol"
+# define CRM_ATTR_FEATURE_SET "#feature-set"
+
+/* Valid operations */
+# define CRM_OP_NOOP "noop"
+# define CRM_OP_JOIN_ANNOUNCE "join_announce"
+# define CRM_OP_JOIN_OFFER "join_offer"
+# define CRM_OP_JOIN_REQUEST "join_request"
+# define CRM_OP_JOIN_ACKNAK "join_ack_nack"
+# define CRM_OP_JOIN_CONFIRM "join_confirm"
+# define CRM_OP_PING "ping"
+# define CRM_OP_NODE_INFO "node-info"
+# define CRM_OP_THROTTLE "throttle"
+# define CRM_OP_VOTE "vote"
+# define CRM_OP_NOVOTE "no-vote"
+# define CRM_OP_HELLO "hello"
+# define CRM_OP_PECALC "pe_calc"
+# define CRM_OP_QUIT "quit"
+# define CRM_OP_LOCAL_SHUTDOWN "start_shutdown"
+# define CRM_OP_SHUTDOWN_REQ "req_shutdown"
+# define CRM_OP_SHUTDOWN "do_shutdown"
+# define CRM_OP_FENCE "stonith"
+# define CRM_OP_REGISTER "register"
+# define CRM_OP_IPC_FWD "ipc_fwd"
+# define CRM_OP_INVOKE_LRM "lrm_invoke"
+# define CRM_OP_LRM_REFRESH "lrm_refresh" //!< Deprecated since 1.1.10
+# define CRM_OP_LRM_DELETE "lrm_delete"
+# define CRM_OP_LRM_FAIL "lrm_fail"
+# define CRM_OP_PROBED "probe_complete"
+# define CRM_OP_REPROBE "probe_again"
+# define CRM_OP_CLEAR_FAILCOUNT "clear_failcount"
+# define CRM_OP_REMOTE_STATE "remote_state"
+# define CRM_OP_RELAXED_SET "one-or-more"
+# define CRM_OP_RELAXED_CLONE "clone-one-or-more"
+# define CRM_OP_RM_NODE_CACHE "rm_node_cache"
+# define CRM_OP_MAINTENANCE_NODES "maintenance_nodes"
+
+/* Possible cluster membership states */
+# define CRMD_JOINSTATE_DOWN "down"
+# define CRMD_JOINSTATE_PENDING "pending"
+# define CRMD_JOINSTATE_MEMBER "member"
+# define CRMD_JOINSTATE_NACK "banned"
+
+# define CRMD_ACTION_DELETE "delete"
+# define CRMD_ACTION_CANCEL "cancel"
+
+# define CRMD_ACTION_RELOAD "reload"
+# define CRMD_ACTION_RELOAD_AGENT "reload-agent"
+# define CRMD_ACTION_MIGRATE "migrate_to"
+# define CRMD_ACTION_MIGRATED "migrate_from"
+
+# define CRMD_ACTION_START "start"
+# define CRMD_ACTION_STARTED "running"
+
+# define CRMD_ACTION_STOP "stop"
+# define CRMD_ACTION_STOPPED "stopped"
+
+# define CRMD_ACTION_PROMOTE "promote"
+# define CRMD_ACTION_PROMOTED "promoted"
+# define CRMD_ACTION_DEMOTE "demote"
+# define CRMD_ACTION_DEMOTED "demoted"
+
+# define CRMD_ACTION_NOTIFY "notify"
+# define CRMD_ACTION_NOTIFIED "notified"
+
+# define CRMD_ACTION_STATUS "monitor"
+# define CRMD_ACTION_METADATA "meta-data"
+# define CRMD_METADATA_CALL_TIMEOUT 30000
+
+/* short names */
+# define RSC_DELETE CRMD_ACTION_DELETE
+# define RSC_CANCEL CRMD_ACTION_CANCEL
+
+# define RSC_MIGRATE CRMD_ACTION_MIGRATE
+# define RSC_MIGRATED CRMD_ACTION_MIGRATED
+
+# define RSC_START CRMD_ACTION_START
+# define RSC_STARTED CRMD_ACTION_STARTED
+
+# define RSC_STOP CRMD_ACTION_STOP
+# define RSC_STOPPED CRMD_ACTION_STOPPED
+
+# define RSC_PROMOTE CRMD_ACTION_PROMOTE
+# define RSC_PROMOTED CRMD_ACTION_PROMOTED
+# define RSC_DEMOTE CRMD_ACTION_DEMOTE
+# define RSC_DEMOTED CRMD_ACTION_DEMOTED
+
+# define RSC_NOTIFY CRMD_ACTION_NOTIFY
+# define RSC_NOTIFIED CRMD_ACTION_NOTIFIED
+
+# define RSC_STATUS CRMD_ACTION_STATUS
+# define RSC_METADATA CRMD_ACTION_METADATA
+/* *INDENT-ON* */
+
+# include <crm/common/cib.h>
+# include <crm/common/logging.h>
+# include <crm/common/util.h>
+
+static inline const char *
+crm_action_str(const char *task, guint interval_ms) {
+ if ((task != NULL) && (interval_ms == 0)
+ && (strcasecmp(task, RSC_STATUS) == 0)) {
+ return "probe";
+ }
+ return task;
+}
+
+#if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1)
+#include <crm/crm_compat.h>
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/crm/crm_compat.h b/include/crm/crm_compat.h
new file mode 100644
index 0000000..2c0a3dd
--- /dev/null
+++ b/include/crm/crm_compat.h
@@ -0,0 +1,61 @@
+/*
+ * 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 PCMK__CRM_CRM_COMPAT__H
+# define PCMK__CRM_CRM_COMPAT__H
+
+#include <glib.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * \file
+ * \brief Deprecated Pacemaker utilities
+ * \ingroup core
+ * \deprecated Do not include this header directly. The utilities in this
+ * header, and the header itself, will be removed in a future
+ * release.
+ */
+
+//! \deprecated Use '\0' instead
+#define EOS '\0'
+
+//! \deprecated This defined constant will be removed in a future release
+#define MAX_IPC_DELAY 120
+
+//! \deprecated This defined constant will be removed in a future release
+#define CRM_OP_LRM_QUERY "lrm_query"
+
+//! \deprecated This defined constant will be removed in a future release
+#define CRM_ATTR_RA_VERSION "#ra-version"
+
+//!@{
+//! \deprecated This macro will be removed in a future release
+
+# ifndef __GNUC__
+# define __builtin_expect(expr, result) (expr)
+# endif
+
+#define __likely(expr) __builtin_expect(expr, 1)
+
+#define __unlikely(expr) __builtin_expect(expr, 0)
+
+// This ends the doxygen deprecation comment
+//!@}
+
+//! \deprecated Use GList * instead
+typedef GList *GListPtr;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // PCMK__CRM_CRM_COMPAT__H
diff --git a/include/crm/fencing/Makefile.am b/include/crm/fencing/Makefile.am
new file mode 100644
index 0000000..12b434d
--- /dev/null
+++ b/include/crm/fencing/Makefile.am
@@ -0,0 +1,13 @@
+#
+# Copyright 2012-2021 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.
+#
+MAINTAINERCLEANFILES = Makefile.in
+
+headerdir=$(pkgincludedir)/crm/fencing
+
+noinst_HEADERS = internal.h
diff --git a/include/crm/fencing/internal.h b/include/crm/fencing/internal.h
new file mode 100644
index 0000000..492c035
--- /dev/null
+++ b/include/crm/fencing/internal.h
@@ -0,0 +1,222 @@
+/*
+ * Copyright 2011-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 STONITH_NG_INTERNAL__H
+# define STONITH_NG_INTERNAL__H
+
+# include <glib.h>
+# include <crm/common/ipc.h>
+# include <crm/common/xml.h>
+# include <crm/common/output_internal.h>
+# include <crm/stonith-ng.h>
+
+enum st_device_flags {
+ st_device_supports_none = (0 << 0),
+ st_device_supports_list = (1 << 0),
+ st_device_supports_status = (1 << 1),
+ st_device_supports_reboot = (1 << 2),
+ st_device_supports_parameter_plug = (1 << 3),
+ st_device_supports_parameter_port = (1 << 4),
+ st_device_supports_on = (1 << 5),
+};
+
+#define stonith__set_device_flags(device_flags, device_id, flags_to_set) do { \
+ device_flags = pcmk__set_flags_as(__func__, __LINE__, LOG_TRACE, \
+ "Fence device", device_id, \
+ (device_flags), (flags_to_set), \
+ #flags_to_set); \
+ } while (0)
+
+#define stonith__set_call_options(st_call_opts, call_for, flags_to_set) do { \
+ st_call_opts = pcmk__set_flags_as(__func__, __LINE__, LOG_TRACE, \
+ "Fencer call", (call_for), \
+ (st_call_opts), (flags_to_set), \
+ #flags_to_set); \
+ } while (0)
+
+#define stonith__clear_call_options(st_call_opts, call_for, flags_to_clear) do { \
+ st_call_opts = pcmk__clear_flags_as(__func__, __LINE__, LOG_TRACE, \
+ "Fencer call", (call_for), \
+ (st_call_opts), (flags_to_clear), \
+ #flags_to_clear); \
+ } while (0)
+
+struct stonith_action_s;
+typedef struct stonith_action_s stonith_action_t;
+
+stonith_action_t *stonith__action_create(const char *agent,
+ const char *action_name,
+ const char *target,
+ uint32_t target_nodeid,
+ int timeout_sec,
+ GHashTable *device_args,
+ GHashTable *port_map,
+ const char *host_arg);
+void stonith__destroy_action(stonith_action_t *action);
+pcmk__action_result_t *stonith__action_result(stonith_action_t *action);
+int stonith__result2rc(const pcmk__action_result_t *result);
+void stonith__xe_set_result(xmlNode *xml, const pcmk__action_result_t *result);
+void stonith__xe_get_result(const xmlNode *xml, pcmk__action_result_t *result);
+xmlNode *stonith__find_xe_with_result(xmlNode *xml);
+
+int stonith__execute_async(stonith_action_t *action, void *userdata,
+ void (*done) (int pid,
+ const pcmk__action_result_t *result,
+ void *user_data),
+ void (*fork_cb) (int pid, void *user_data));
+
+int stonith__metadata_async(const char *agent, int timeout_sec,
+ void (*callback)(int pid,
+ const pcmk__action_result_t *result,
+ void *user_data),
+ void *user_data);
+
+xmlNode *create_level_registration_xml(const char *node, const char *pattern,
+ const char *attr, const char *value,
+ int level,
+ const stonith_key_value_t *device_list);
+
+xmlNode *create_device_registration_xml(const char *id,
+ enum stonith_namespace namespace,
+ const char *agent,
+ const stonith_key_value_t *params,
+ const char *rsc_provides);
+
+void stonith__register_messages(pcmk__output_t *out);
+
+GList *stonith__parse_targets(const char *hosts);
+
+const char *stonith__later_succeeded(const stonith_history_t *event,
+ const stonith_history_t *top_history);
+stonith_history_t *stonith__sort_history(stonith_history_t *history);
+
+void stonith__device_parameter_flags(uint32_t *device_flags,
+ const char *device_name,
+ xmlNode *metadata);
+
+# define ST_LEVEL_MAX 10
+
+# define F_STONITH_CLIENTID "st_clientid"
+# define F_STONITH_CALLOPTS "st_callopt"
+# define F_STONITH_CALLID "st_callid"
+# define F_STONITH_CALLDATA "st_calldata"
+# define F_STONITH_OPERATION "st_op"
+# define F_STONITH_TARGET "st_target"
+# define F_STONITH_REMOTE_OP_ID "st_remote_op"
+# define F_STONITH_REMOTE_OP_ID_RELAY "st_remote_op_relay"
+# define F_STONITH_RC "st_rc"
+# define F_STONITH_OUTPUT "st_output"
+/*! Timeout period per a device execution */
+# define F_STONITH_TIMEOUT "st_timeout"
+# define F_STONITH_TOLERANCE "st_tolerance"
+# define F_STONITH_DELAY "st_delay"
+/*! Action specific timeout period returned in query of fencing devices. */
+# define F_STONITH_ACTION_TIMEOUT "st_action_timeout"
+/*! Host in query result is not allowed to run this action */
+# define F_STONITH_ACTION_DISALLOWED "st_action_disallowed"
+/*! Maximum of random fencing delay for a device */
+# define F_STONITH_DELAY_MAX "st_delay_max"
+/*! Base delay used for a fencing delay */
+# define F_STONITH_DELAY_BASE "st_delay_base"
+/*! Has this device been verified using a monitor type
+ * operation (monitor, list, status) */
+# define F_STONITH_DEVICE_VERIFIED "st_monitor_verified"
+/*! device is required for this action */
+# define F_STONITH_DEVICE_REQUIRED "st_required"
+/*! number of available devices in query result */
+# define F_STONITH_AVAILABLE_DEVICES "st-available-devices"
+# define F_STONITH_CALLBACK_TOKEN "st_async_id"
+# define F_STONITH_CLIENTNAME "st_clientname"
+# define F_STONITH_CLIENTNODE "st_clientnode"
+# define F_STONITH_NOTIFY_ACTIVATE "st_notify_activate"
+# define F_STONITH_NOTIFY_DEACTIVATE "st_notify_deactivate"
+# define F_STONITH_DELEGATE "st_delegate"
+# define F_STONITH_DEVICE_SUPPORT_FLAGS "st_device_support_flags"
+/*! The node initiating the stonith operation. If an operation
+ * is relayed, this is the last node the operation lands on. When
+ * in standalone mode, origin is the client's id that originated the
+ * operation. */
+# define F_STONITH_ORIGIN "st_origin"
+# define F_STONITH_HISTORY_LIST "st_history"
+# define F_STONITH_DATE "st_date"
+# define F_STONITH_DATE_NSEC "st_date_nsec"
+# define F_STONITH_STATE "st_state"
+# define F_STONITH_ACTIVE "st_active"
+# define F_STONITH_DIFFERENTIAL "st_differential"
+
+# define F_STONITH_DEVICE "st_device_id"
+# define F_STONITH_ACTION "st_device_action"
+# define F_STONITH_MERGED "st_op_merged"
+
+# define T_STONITH_NG "stonith-ng"
+# define T_STONITH_REPLY "st-reply"
+/*! For async operations, an event from the server containing
+ * the total amount of time the server is allowing for the operation
+ * to take place is returned to the client. */
+# define T_STONITH_TIMEOUT_VALUE "st-async-timeout-value"
+# define T_STONITH_NOTIFY "st_notify"
+
+# define STONITH_ATTR_ACTION_OP "action"
+
+# define STONITH_OP_EXEC "st_execute"
+# define STONITH_OP_TIMEOUT_UPDATE "st_timeout_update"
+# define STONITH_OP_QUERY "st_query"
+# define STONITH_OP_FENCE "st_fence"
+# define STONITH_OP_RELAY "st_relay"
+# define STONITH_OP_DEVICE_ADD "st_device_register"
+# define STONITH_OP_DEVICE_DEL "st_device_remove"
+# define STONITH_OP_FENCE_HISTORY "st_fence_history"
+# define STONITH_OP_LEVEL_ADD "st_level_add"
+# define STONITH_OP_LEVEL_DEL "st_level_remove"
+
+# define STONITH_WATCHDOG_AGENT "fence_watchdog"
+/* Don't change 2 below as it would break rolling upgrade */
+# define STONITH_WATCHDOG_AGENT_INTERNAL "#watchdog"
+# define STONITH_WATCHDOG_ID "watchdog"
+
+stonith_history_t *stonith__first_matching_event(stonith_history_t *history,
+ bool (*matching_fn)(stonith_history_t *, void *),
+ void *user_data);
+bool stonith__event_state_pending(stonith_history_t *history, void *user_data);
+bool stonith__event_state_eq(stonith_history_t *history, void *user_data);
+bool stonith__event_state_neq(stonith_history_t *history, void *user_data);
+
+int stonith__legacy2status(int rc);
+
+int stonith__exit_status(const stonith_callback_data_t *data);
+int stonith__execution_status(const stonith_callback_data_t *data);
+const char *stonith__exit_reason(const stonith_callback_data_t *data);
+
+int stonith__event_exit_status(const stonith_event_t *event);
+int stonith__event_execution_status(const stonith_event_t *event);
+const char *stonith__event_exit_reason(const stonith_event_t *event);
+char *stonith__event_description(const stonith_event_t *event);
+gchar *stonith__history_description(const stonith_history_t *event,
+ bool full_history,
+ const char *later_succeeded,
+ uint32_t show_opts);
+
+/*!
+ * \internal
+ * \brief Is a fencing operation in pending state?
+ *
+ * \param[in] state State as enum op_state value
+ *
+ * \return A boolean
+ */
+static inline bool
+stonith__op_state_pending(enum op_state state)
+{
+ return state != st_failed && state != st_done;
+}
+
+gboolean stonith__watchdog_fencing_enabled_for_node(const char *node);
+gboolean stonith__watchdog_fencing_enabled_for_node_api(stonith_t *st, const char *node);
+
+#endif
diff --git a/include/crm/lrmd.h b/include/crm/lrmd.h
new file mode 100644
index 0000000..dfc2f25
--- /dev/null
+++ b/include/crm/lrmd.h
@@ -0,0 +1,628 @@
+/*
+ * Copyright 2012-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 PCMK__CRM_LRMD__H
+# define PCMK__CRM_LRMD__H
+
+#include <stdbool.h> // bool
+#include <glib.h> // guint, GList
+#include <crm_config.h>
+#include <crm/services.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * \file
+ * \brief Resource agent executor
+ * \ingroup lrmd
+ */
+
+typedef struct lrmd_s lrmd_t;
+typedef struct lrmd_key_value_s {
+ char *key;
+ char *value;
+ struct lrmd_key_value_s *next;
+} lrmd_key_value_t;
+
+/* This should be bumped every time there is an incompatible change that
+ * prevents older clients from connecting to this version of the server.
+ */
+#define LRMD_PROTOCOL_VERSION "1.1"
+
+/* This is the version that the client version will actually be compared
+ * against. This should be identical to LRMD_PROTOCOL_VERSION. However, we
+ * accidentally bumped LRMD_PROTOCOL_VERSION in 6424a647 (1.1.15) when we didn't
+ * need to, so for now it's different. If we ever have a truly incompatible
+ * bump, we can drop this and compare against LRMD_PROTOCOL_VERSION.
+ */
+#define LRMD_MIN_PROTOCOL_VERSION "1.0"
+
+/* *INDENT-OFF* */
+#define DEFAULT_REMOTE_KEY_LOCATION PACEMAKER_CONFIG_DIR "/authkey"
+#define ALT_REMOTE_KEY_LOCATION "/etc/corosync/authkey"
+#define DEFAULT_REMOTE_PORT 3121
+#define DEFAULT_REMOTE_USERNAME "lrmd"
+
+#define F_LRMD_OPERATION "lrmd_op"
+#define F_LRMD_CLIENTNAME "lrmd_clientname"
+#define F_LRMD_IS_IPC_PROVIDER "lrmd_is_ipc_provider"
+#define F_LRMD_CLIENTID "lrmd_clientid"
+#define F_LRMD_PROTOCOL_VERSION "lrmd_protocol_version"
+#define F_LRMD_REMOTE_MSG_TYPE "lrmd_remote_msg_type"
+#define F_LRMD_REMOTE_MSG_ID "lrmd_remote_msg_id"
+#define F_LRMD_CALLBACK_TOKEN "lrmd_async_id"
+#define F_LRMD_CALLID "lrmd_callid"
+#define F_LRMD_CALLOPTS "lrmd_callopt"
+#define F_LRMD_CALLDATA "lrmd_calldata"
+#define F_LRMD_RC "lrmd_rc"
+#define F_LRMD_EXEC_RC "lrmd_exec_rc"
+#define F_LRMD_OP_STATUS "lrmd_exec_op_status"
+#define F_LRMD_TIMEOUT "lrmd_timeout"
+#define F_LRMD_WATCHDOG "lrmd_watchdog"
+#define F_LRMD_CLASS "lrmd_class"
+#define F_LRMD_PROVIDER "lrmd_provider"
+#define F_LRMD_TYPE "lrmd_type"
+#define F_LRMD_ORIGIN "lrmd_origin"
+
+#define F_LRMD_RSC_RUN_TIME "lrmd_run_time"
+#define F_LRMD_RSC_RCCHANGE_TIME "lrmd_rcchange_time"
+#define F_LRMD_RSC_EXEC_TIME "lrmd_exec_time"
+#define F_LRMD_RSC_QUEUE_TIME "lrmd_queue_time"
+
+#define F_LRMD_RSC_ID "lrmd_rsc_id"
+#define F_LRMD_RSC_ACTION "lrmd_rsc_action"
+#define F_LRMD_RSC_USERDATA_STR "lrmd_rsc_userdata_str"
+#define F_LRMD_RSC_OUTPUT "lrmd_rsc_output"
+#define F_LRMD_RSC_EXIT_REASON "lrmd_rsc_exit_reason"
+#define F_LRMD_RSC_START_DELAY "lrmd_rsc_start_delay"
+#define F_LRMD_RSC_INTERVAL "lrmd_rsc_interval"
+#define F_LRMD_RSC_DELETED "lrmd_rsc_deleted"
+#define F_LRMD_RSC "lrmd_rsc"
+
+#define F_LRMD_ALERT_ID "lrmd_alert_id"
+#define F_LRMD_ALERT_PATH "lrmd_alert_path"
+#define F_LRMD_ALERT "lrmd_alert"
+
+#define LRMD_OP_RSC_REG "lrmd_rsc_register"
+#define LRMD_OP_RSC_EXEC "lrmd_rsc_exec"
+#define LRMD_OP_RSC_CANCEL "lrmd_rsc_cancel"
+#define LRMD_OP_RSC_UNREG "lrmd_rsc_unregister"
+#define LRMD_OP_RSC_INFO "lrmd_rsc_info"
+#define LRMD_OP_RSC_METADATA "lrmd_rsc_metadata"
+#define LRMD_OP_POKE "lrmd_rsc_poke"
+#define LRMD_OP_NEW_CLIENT "lrmd_rsc_new_client"
+#define LRMD_OP_CHECK "lrmd_check"
+#define LRMD_OP_ALERT_EXEC "lrmd_alert_exec"
+#define LRMD_OP_GET_RECURRING "lrmd_get_recurring"
+
+#define LRMD_IPC_OP_NEW "new"
+#define LRMD_IPC_OP_DESTROY "destroy"
+#define LRMD_IPC_OP_EVENT "event"
+#define LRMD_IPC_OP_REQUEST "request"
+#define LRMD_IPC_OP_RESPONSE "response"
+#define LRMD_IPC_OP_SHUTDOWN_REQ "shutdown_req"
+#define LRMD_IPC_OP_SHUTDOWN_ACK "shutdown_ack"
+#define LRMD_IPC_OP_SHUTDOWN_NACK "shutdown_nack"
+
+#define F_LRMD_IPC_OP "lrmd_ipc_op"
+#define F_LRMD_IPC_IPC_SERVER "lrmd_ipc_server"
+#define F_LRMD_IPC_SESSION "lrmd_ipc_session"
+#define F_LRMD_IPC_CLIENT "lrmd_ipc_client"
+#define F_LRMD_IPC_USER "lrmd_ipc_user"
+#define F_LRMD_IPC_MSG "lrmd_ipc_msg"
+#define F_LRMD_IPC_MSG_ID "lrmd_ipc_msg_id"
+#define F_LRMD_IPC_MSG_FLAGS "lrmd_ipc_msg_flags"
+
+#define T_LRMD "lrmd"
+#define T_LRMD_REPLY "lrmd_reply"
+#define T_LRMD_NOTIFY "lrmd_notify"
+#define T_LRMD_IPC_PROXY "lrmd_ipc_proxy"
+#define T_LRMD_RSC_OP "lrmd_rsc_op"
+/* *INDENT-ON* */
+
+/*!
+ * \brief Create a new connection to the local executor
+ */
+lrmd_t *lrmd_api_new(void);
+
+/*!
+ * \brief Create a new TLS connection to a remote executor
+ *
+ * \param[in] nodename Name of remote node identified with this connection
+ * \param[in] server Hostname to connect to
+ * \param[in] port Port number to connect to (or 0 to use default)
+ *
+ * \return Newly created executor connection object
+ * \note If only one of \p nodename and \p server is non-NULL, it will be used
+ * for both purposes. If both are NULL, a local IPC connection will be
+ * created instead.
+ */
+lrmd_t *lrmd_remote_api_new(const char *nodename, const char *server, int port);
+
+/*!
+ * \brief Use after lrmd_poll returns 1 to read and dispatch a message
+ *
+ * \param[in,out] lrmd Executor connection object
+ *
+ * \return TRUE if connection is still up, FALSE if disconnected
+ */
+bool lrmd_dispatch(lrmd_t *lrmd);
+
+/*!
+ * \brief Check whether a message is available on an executor connection
+ *
+ * \param[in,out] lrmd Executor connection object to check
+ * \param[in] timeout Currently ignored
+ *
+ * \retval 1 Message is ready
+ * \retval 0 Timeout occurred
+ * \retval negative errno Error occurred
+ *
+ * \note This is intended for callers that do not use a main loop.
+ */
+int lrmd_poll(lrmd_t *lrmd, int timeout);
+
+/*!
+ * \brief Destroy executor connection object
+ *
+ * \param[in,out] lrmd Executor connection object to destroy
+ */
+void lrmd_api_delete(lrmd_t *lrmd);
+
+lrmd_key_value_t *lrmd_key_value_add(lrmd_key_value_t * kvp, const char *key, const char *value);
+
+enum lrmd_call_options {
+ lrmd_opt_none = 0,
+
+ //! Notify only the client that made the request (rather than all clients)
+ lrmd_opt_notify_orig_only = (1 << 1),
+
+ /*!
+ * Drop recurring operations initiated by a client when the client
+ * disconnects. This option is only valid when registering a resource. When
+ * used with a connection to a remote executor, recurring operations will be
+ * dropped once all remote connections disconnect.
+ *
+ * @COMPAT This is broken, because these values should be unique bits, and
+ * this value overlaps lrmd_opt_notify_orig_only (0x02). The impact is low
+ * since this value is used only with registration requests and the other
+ * one is used only with execution requests. Regardless, when we can break
+ * API compatibility, this should be changed to (1 << 0) or (1 << 3).
+ */
+ lrmd_opt_drop_recurring = 0x00000003,
+
+ //! Send notifications for recurring operations only when the result changes
+ lrmd_opt_notify_changes_only = (1 << 2),
+};
+
+enum lrmd_callback_event {
+ lrmd_event_register,
+ lrmd_event_unregister,
+ lrmd_event_exec_complete,
+ lrmd_event_disconnect,
+ lrmd_event_connect,
+ lrmd_event_poke,
+ lrmd_event_new_client,
+};
+
+typedef struct lrmd_event_data_s {
+ /*! Type of event, register, unregister, call_completed... */
+ enum lrmd_callback_event type;
+
+ /*! The resource this event occurred on. */
+ const char *rsc_id;
+ /*! The action performed, start, stop, monitor... */
+ const char *op_type;
+ /*! The user data passed by caller of exec() API function */
+ const char *user_data;
+
+ /*! The client api call id associated with this event */
+ int call_id;
+ /*! The operation's timeout period in ms. */
+ int timeout;
+ /*! The operation's recurring interval in ms. */
+ guint interval_ms;
+ /*! The operation's start delay value in ms. */
+ int start_delay;
+ /*! This operation that just completed is on a deleted rsc. */
+ int rsc_deleted;
+
+ /*! The executed ra return code mapped to OCF */
+ enum ocf_exitcode rc;
+ /*! The executor status returned for exec_complete events */
+ int op_status;
+ /*! stdout from resource agent operation */
+ const char *output;
+ /*! Timestamp of when op ran */
+ unsigned int t_run;
+ /*! Timestamp of last rc change */
+ unsigned int t_rcchange;
+ /*! Time in length op took to execute */
+ unsigned int exec_time;
+ /*! Time in length spent in queue */
+ unsigned int queue_time;
+
+ /*! int connection result. Used for connection and poke events */
+ int connection_rc;
+
+ /* This is a GHashTable containing the
+ * parameters given to the operation */
+ void *params;
+
+ /*! client node name associated with this connection
+ * (used to match actions to the proper client when there are multiple)
+ */
+ const char *remote_nodename;
+
+ /*! exit failure reason string from resource agent operation */
+ const char *exit_reason;
+} lrmd_event_data_t;
+
+lrmd_event_data_t *lrmd_new_event(const char *rsc_id, const char *task,
+ guint interval_ms);
+lrmd_event_data_t *lrmd_copy_event(lrmd_event_data_t * event);
+void lrmd_free_event(lrmd_event_data_t * event);
+
+typedef struct lrmd_rsc_info_s {
+ char *id;
+ char *type;
+ char *standard;
+ char *provider;
+} lrmd_rsc_info_t;
+
+typedef struct lrmd_op_info_s {
+ char *rsc_id;
+ char *action;
+ char *interval_ms_s;
+ char *timeout_ms_s;
+} lrmd_op_info_t;
+
+lrmd_rsc_info_t *lrmd_new_rsc_info(const char *rsc_id, const char *standard,
+ const char *provider, const char *type);
+lrmd_rsc_info_t *lrmd_copy_rsc_info(lrmd_rsc_info_t * rsc_info);
+void lrmd_free_rsc_info(lrmd_rsc_info_t * rsc_info);
+void lrmd_free_op_info(lrmd_op_info_t *op_info);
+
+typedef void (*lrmd_event_callback) (lrmd_event_data_t * event);
+
+typedef struct lrmd_list_s {
+ const char *val;
+ struct lrmd_list_s *next;
+} lrmd_list_t;
+
+void lrmd_list_freeall(lrmd_list_t * head);
+void lrmd_key_value_freeall(lrmd_key_value_t * head);
+
+typedef struct lrmd_api_operations_s {
+ /*!
+ * \brief Connect to an executor
+ *
+ * \param[in,out] lrmd Executor connection object
+ * \param[in] client_name Arbitrary identifier to pass to server
+ * \param[out] fd If not NULL, where to store file descriptor
+ * for connection's socket
+ *
+ * \return Legacy Pacemaker return code
+ */
+ int (*connect) (lrmd_t *lrmd, const char *client_name, int *fd);
+
+ /*!
+ * \brief Initiate an executor connection without blocking
+ *
+ * \param[in,out] lrmd Executor connection object
+ * \param[in] client_name Arbitrary identifier to pass to server
+ * \param[in] timeout Error if not connected within this time
+ * (milliseconds)
+ *
+ * \return Legacy Pacemaker return code (if pcmk_ok, the event callback will
+ * be called later with the result)
+ * \note This function requires a mainloop.
+ */
+ int (*connect_async) (lrmd_t *lrmd, const char *client_name,
+ int timeout /*ms */ );
+
+ /*!
+ * \brief Check whether connection to executor daemon is (still) active
+ *
+ * \param[in,out] lrmd Executor connection object to check
+ *
+ * \return 1 if the executor connection is active, 0 otherwise
+ */
+ int (*is_connected) (lrmd_t *lrmd);
+
+ /*!
+ * \brief Poke executor connection to verify it is still active
+ *
+ * \param[in,out] lrmd Executor connection object to check
+ *
+ * \return Legacy Pacemaker return code (if pcmk_ok, the event callback will
+ * be called later with the result)
+ * \note The response comes in the form of a poke event to the callback.
+ *
+ */
+ int (*poke_connection) (lrmd_t *lrmd);
+
+ /*!
+ * \brief Disconnect from the executor.
+ *
+ * \param[in,out] lrmd Executor connection object to disconnect
+ *
+ * \return Legacy Pacemaker return code
+ */
+ int (*disconnect) (lrmd_t *lrmd);
+
+ /*!
+ * \brief Register a resource with the executor
+ *
+ * \param[in,out] lrmd Executor connection object
+ * \param[in] rsc_id ID of resource to register
+ * \param[in] standard Resource's resource agent standard
+ * \param[in] provider Resource's resource agent provider (or NULL)
+ * \param[in] agent Resource's resource agent name
+ * \param[in] options Group of enum lrmd_call_options flags
+ *
+ * \note Synchronous, guaranteed to occur in daemon before function returns.
+ *
+ * \return Legacy Pacemaker return code
+ */
+ int (*register_rsc) (lrmd_t *lrmd, const char *rsc_id, const char *standard,
+ const char *provider, const char *agent,
+ enum lrmd_call_options options);
+
+ /*!
+ * \brief Retrieve a resource's registration information
+ *
+ * \param[in,out] lrmd Executor connection object
+ * \param[in] rsc_id ID of resource to check
+ * \param[in] options Group of enum lrmd_call_options flags
+ *
+ * \return Resource information on success, otherwise NULL
+ */
+ lrmd_rsc_info_t *(*get_rsc_info) (lrmd_t *lrmd, const char *rsc_id,
+ enum lrmd_call_options options);
+
+ /*!
+ * \brief Retrieve recurring operations registered for a resource
+ *
+ * \param[in,out] lrmd Executor connection object
+ * \param[in] rsc_id ID of resource to check
+ * \param[in] timeout_ms Error if not completed within this time
+ * \param[in] options Group of enum lrmd_call_options flags
+ * \param[out] output Where to store list of lrmd_op_info_t
+ *
+ * \return Legacy Pacemaker return code
+ */
+ int (*get_recurring_ops) (lrmd_t *lrmd, const char *rsc_id, int timeout_ms,
+ enum lrmd_call_options options, GList **output);
+
+ /*!
+ * \brief Unregister a resource from the executor
+ *
+ * \param[in,out] lrmd Executor connection object
+ * \param[in] rsc_id ID of resource to unregister
+ * \param[in] options Group of enum lrmd_call_options flags
+ *
+ * \return Legacy Pacemaker return code (of particular interest, EINPROGRESS
+ * means that operations are in progress for the resource, and the
+ * unregistration will be done when they complete)
+ * \note Pending and recurring operations will be cancelled.
+ * \note Synchronous, guaranteed to occur in daemon before function returns.
+ *
+ */
+ int (*unregister_rsc) (lrmd_t *lrmd, const char *rsc_id,
+ enum lrmd_call_options options);
+
+ /*!
+ * \brief Set a callback for executor events
+ *
+ * \param[in,out] lrmd Executor connection object
+ * \param[in] callback Callback to set
+ */
+ void (*set_callback) (lrmd_t *lrmd, lrmd_event_callback callback);
+
+ /*!
+ * \brief Request execution of a resource action
+ *
+ * \param[in,out] lrmd Executor connection object
+ * \param[in] rsc_id ID of resource
+ * \param[in] action Name of resource action to execute
+ * \param[in] userdata Arbitrary string to pass to event callback
+ * \param[in] interval_ms If 0, execute action once, otherwise
+ * recurring at this interval (in milliseconds)
+ * \param[in] timeout Error if not complete within this time (in
+ * milliseconds)
+ * \param[in] start_delay Wait this long before execution (in
+ * milliseconds)
+ * \param[in] options Group of enum lrmd_call_options flags
+ * \param[in,out] params Parameters to pass to agent (will be freed)
+ *
+ * \return A call ID for the action on success (in which case the action is
+ * queued in the executor, and the event callback will be called
+ * later with the result), otherwise a negative legacy Pacemaker
+ * return code
+ * \note exec() and cancel() operations on an individual resource are
+ * guaranteed to occur in the order the client API is called. However,
+ * operations on different resources are not guaranteed to occur in
+ * any specific order.
+ */
+ int (*exec) (lrmd_t *lrmd, const char *rsc_id, const char *action,
+ const char *userdata, guint interval_ms, int timeout,
+ int start_delay, enum lrmd_call_options options,
+ lrmd_key_value_t *params);
+
+ /*!
+ * \brief Cancel a recurring resource action
+ *
+ * \param[in,out] lrmd Executor connection object
+ * \param[in] rsc_id ID of resource
+ * \param[in] action Name of resource action to cancel
+ * \param[in] interval_ms Action's interval (in milliseconds)
+ *
+ * \return Legacy Pacemaker return code (if pcmk_ok, cancellation is queued
+ * on function return, and the event callback will be called later
+ * with an exec_complete event with an lrmd_op_status signifying
+ * that the operation is cancelled)
+ *
+ * \note exec() and cancel() operations on an individual resource are
+ * guaranteed to occur in the order the client API is called. However,
+ * operations on different resources are not guaranteed to occur in
+ * any specific order.
+ */
+ int (*cancel) (lrmd_t *lrmd, const char *rsc_id, const char *action,
+ guint interval_ms);
+
+ /*!
+ * \brief Retrieve resource agent metadata synchronously
+ *
+ * \param[in] lrmd Executor connection (unused)
+ * \param[in] standard Resource agent class
+ * \param[in] provider Resource agent provider
+ * \param[in] agent Resource agent type
+ * \param[out] output Where to store metadata (must not be NULL)
+ * \param[in] options Group of enum lrmd_call_options flags (unused)
+ *
+ * \return Legacy Pacemaker return code
+ *
+ * \note Caller is responsible for freeing output. This call is always
+ * synchronous (blocking), and always done directly by the library
+ * (not via the executor connection). This means that it is based on
+ * the local host environment, even if the executor connection is to a
+ * remote node, so this may fail if the agent is not installed
+ * locally. This also means that, if an external agent must be
+ * executed, it will be executed by the caller's user, not the
+ * executor's.
+ */
+ int (*get_metadata) (lrmd_t *lrmd, const char *standard,
+ const char *provider, const char *agent,
+ char **output, enum lrmd_call_options options);
+
+ /*!
+ * \brief Retrieve a list of installed resource agents
+ *
+ * \param[in] lrmd Executor connection (unused)
+ * \param[out] agents Where to store agent list (must not be NULL)
+ * \param[in] standard Resource agent standard to list
+ * \param[in] provider Resource agent provider to list (or NULL)
+ *
+ * \return Number of items in list on success, negative legacy Pacemaker
+ * return code otherwise
+ *
+ * \note if standard is not provided, all known agents will be returned
+ * \note list must be freed using lrmd_list_freeall()
+ */
+ int (*list_agents) (lrmd_t *lrmd, lrmd_list_t **agents,
+ const char *standard, const char *provider);
+
+ /*!
+ * \brief Retrieve a list of resource agent providers
+ *
+ * \param[in] lrmd Executor connection (unused)
+ * \param[in] agent If not NULL, list providers for this agent only
+ * \param[out] providers Where to store provider list
+ *
+ * \return Number of items in list on success, negative legacy Pacemaker
+ * return code otherwise
+ * \note The caller is responsible for freeing *providers with
+ * lrmd_list_freeall().
+ */
+ int (*list_ocf_providers) (lrmd_t *lrmd, const char *agent,
+ lrmd_list_t **providers);
+
+ /*!
+ * \brief Retrieve a list of supported standards
+ *
+ * \param[in] lrmd Executor connection (unused)
+ * \param[out] standards Where to store standards list
+ *
+ * \return Number of items in list on success, negative legacy Pacemaker
+ * return code otherwise
+ * \note The caller is responsible for freeing *standards with
+ * lrmd_list_freeall().
+ */
+ int (*list_standards) (lrmd_t *lrmd, lrmd_list_t **standards);
+
+ /*!
+ * \brief Execute an alert agent
+ *
+ * \param[in,out] lrmd Executor connection
+ * \param[in] alert_id Name of alert to execute
+ * \param[in] alert_path Full path to alert executable
+ * \param[in] timeout Error if not complete within this many
+ * milliseconds
+ * \param[in,out] params Parameters to pass to agent (will be freed)
+ *
+ * \return Legacy Pacemaker return code (if pcmk_ok, the alert is queued in
+ * the executor, and the event callback will be called later with
+ * the result)
+ *
+ * \note Operations on individual alerts (by ID) are guaranteed to occur in
+ * the order the client API is called. Operations on different alerts
+ * are not guaranteed to occur in any specific order.
+ */
+ int (*exec_alert) (lrmd_t *lrmd, const char *alert_id,
+ const char *alert_path, int timeout,
+ lrmd_key_value_t *params);
+
+ /*!
+ * \brief Retrieve resource agent metadata synchronously with parameters
+ *
+ * \param[in] lrmd Executor connection (unused)
+ * \param[in] standard Resource agent class
+ * \param[in] provider Resource agent provider
+ * \param[in] agent Resource agent type
+ * \param[out] output Where to store metadata (must not be NULL)
+ * \param[in] options Group of enum lrmd_call_options flags (unused)
+ * \param[in,out] params Parameters to pass to agent (will be freed)
+ *
+ * \return Legacy Pacemaker return code
+ *
+ * \note This is identical to the get_metadata() API call, except parameters
+ * will be passed to the resource agent via environment variables.
+ */
+ int (*get_metadata_params) (lrmd_t *lrmd, const char *standard,
+ const char *provider, const char *agent,
+ char **output, enum lrmd_call_options options,
+ lrmd_key_value_t *params);
+
+} lrmd_api_operations_t;
+
+struct lrmd_s {
+ lrmd_api_operations_t *cmds;
+ void *lrmd_private;
+};
+
+static inline const char *
+lrmd_event_type2str(enum lrmd_callback_event type)
+{
+ switch (type) {
+ case lrmd_event_register:
+ return "register";
+ case lrmd_event_unregister:
+ return "unregister";
+ case lrmd_event_exec_complete:
+ return "exec_complete";
+ case lrmd_event_disconnect:
+ return "disconnect";
+ case lrmd_event_connect:
+ return "connect";
+ case lrmd_event_poke:
+ return "poke";
+ case lrmd_event_new_client:
+ return "new_client";
+ }
+ return "unknown";
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/crm/lrmd_internal.h b/include/crm/lrmd_internal.h
new file mode 100644
index 0000000..5810554
--- /dev/null
+++ b/include/crm/lrmd_internal.h
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2015-2023 the Pacemaker project contributors
+ *
+ * The version control history for this file may have further details.
+ *
+ * This source code is licensed under the GNU Lesser General Public License
+ * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
+ */
+
+#ifndef LRMD_INTERNAL__H
+#define LRMD_INTERNAL__H
+
+#include <stdint.h> // uint32_t
+#include <glib.h> // GList, GHashTable, gpointer
+#include <libxml/tree.h> // xmlNode
+#include <crm/common/ipc.h> // crm_ipc_t
+#include <crm/common/mainloop.h> // mainloop_io_t, ipc_client_callbacks
+#include <crm/common/output_internal.h> // pcmk__output_t
+#include <crm/common/remote_internal.h> // pcmk__remote_t
+#include <crm/lrmd.h> // lrmd_t, lrmd_event_data_t, lrmd_rsc_info_t
+
+int lrmd__new(lrmd_t **api, const char *nodename, const char *server, int port);
+
+int lrmd_send_attribute_alert(lrmd_t *lrmd, const GList *alert_list,
+ const char *node, uint32_t nodeid,
+ const char *attr_name, const char *attr_value);
+int lrmd_send_node_alert(lrmd_t *lrmd, const GList *alert_list,
+ const char *node, uint32_t nodeid, const char *state);
+int lrmd_send_fencing_alert(lrmd_t *lrmd, const GList *alert_list,
+ const char *target, const char *task,
+ const char *desc, int op_rc);
+int lrmd_send_resource_alert(lrmd_t *lrmd, const GList *alert_list,
+ const char *node, const lrmd_event_data_t *op);
+
+int lrmd__remote_send_xml(pcmk__remote_t *session, xmlNode *msg, uint32_t id,
+ const char *msg_type);
+
+int lrmd__metadata_async(const lrmd_rsc_info_t *rsc,
+ void (*callback)(int pid,
+ const pcmk__action_result_t *result,
+ void *user_data),
+ void *user_data);
+
+void lrmd__set_result(lrmd_event_data_t *event, enum ocf_exitcode rc,
+ int op_status, const char *exit_reason);
+
+void lrmd__reset_result(lrmd_event_data_t *event);
+
+time_t lrmd__uptime(lrmd_t *lrmd);
+
+/* Shared functions for IPC proxy back end */
+
+typedef struct remote_proxy_s {
+ char *node_name;
+ char *session_id;
+
+ gboolean is_local;
+
+ crm_ipc_t *ipc;
+ mainloop_io_t *source;
+ uint32_t last_request_id;
+ lrmd_t *lrm;
+
+} remote_proxy_t;
+
+remote_proxy_t *remote_proxy_new(lrmd_t *lrmd,
+ struct ipc_client_callbacks *proxy_callbacks,
+ const char *node_name, const char *session_id,
+ const char *channel);
+
+int lrmd__validate_remote_settings(lrmd_t *lrmd, GHashTable *hash);
+void remote_proxy_cb(lrmd_t *lrmd, const char *node_name, xmlNode *msg);
+void remote_proxy_ack_shutdown(lrmd_t *lrmd);
+void remote_proxy_nack_shutdown(lrmd_t *lrmd);
+
+int remote_proxy_dispatch(const char *buffer, ssize_t length,
+ gpointer userdata);
+void remote_proxy_disconnected(gpointer data);
+void remote_proxy_free(gpointer data);
+
+void remote_proxy_relay_event(remote_proxy_t *proxy, xmlNode *msg);
+void remote_proxy_relay_response(remote_proxy_t *proxy, xmlNode *msg,
+ int msg_id);
+
+void lrmd__register_messages(pcmk__output_t *out);
+
+#ifdef HAVE_GNUTLS_GNUTLS_H
+int lrmd__init_remote_key(gnutls_datum_t *key);
+#endif
+
+#endif
diff --git a/include/crm/msg_xml.h b/include/crm/msg_xml.h
new file mode 100644
index 0000000..2e50adb
--- /dev/null
+++ b/include/crm/msg_xml.h
@@ -0,0 +1,487 @@
+/*
+ * Copyright 2004-2023 the Pacemaker project contributors
+ *
+ * The version control history for this file may have further details.
+ *
+ * This source code is licensed under the GNU Lesser General Public License
+ * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
+ */
+
+#ifndef PCMK__CRM_MSG_XML__H
+# define PCMK__CRM_MSG_XML__H
+
+# include <crm/common/xml.h>
+
+#if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1)
+#include <crm/msg_xml_compat.h>
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* This file defines constants for various XML syntax (mainly element and
+ * attribute names).
+ *
+ * For consistency, new constants should start with "PCMK_", followed by "XE"
+ * for XML element names, "XA" for XML attribute names, and "META" for meta
+ * attribute names. Old names that don't follow this policy should eventually be
+ * deprecated and replaced with names that do.
+ */
+
+/*
+ * XML elements
+ */
+
+#define PCMK_XE_DATE_EXPRESSION "date_expression"
+#define PCMK_XE_OP_EXPRESSION "op_expression"
+
+/* This has been deprecated as a CIB element (an alias for <clone> with
+ * "promotable" set to "true") since 2.0.0.
+ */
+#define PCMK_XE_PROMOTABLE_LEGACY "master"
+
+#define PCMK_XE_RSC_EXPRESSION "rsc_expression"
+
+
+/*
+ * XML attributes
+ */
+
+/* These have been deprecated as CIB <clone> element attributes (aliases for
+ * "promoted-max" and "promoted-node-max") since 2.0.0.
+ */
+#define PCMK_XA_PROMOTED_MAX_LEGACY "master-max"
+#define PCMK_XA_PROMOTED_NODE_MAX_LEGACY "master-node-max"
+
+
+/*
+ * Meta attributes
+ */
+
+#define PCMK_META_ENABLED "enabled"
+
+
+/*
+ * Older constants that don't follow current naming
+ */
+
+# ifndef F_ORIG
+# define F_ORIG "src"
+# endif
+
+# ifndef F_SEQ
+# define F_SEQ "seq"
+# endif
+
+# ifndef F_SUBTYPE
+# define F_SUBTYPE "subt"
+# endif
+
+# ifndef F_TYPE
+# define F_TYPE "t"
+# endif
+
+# ifndef F_CLIENTNAME
+# define F_CLIENTNAME "cn"
+# endif
+
+# ifndef F_XML_TAGNAME
+# define F_XML_TAGNAME "__name__"
+# endif
+
+# ifndef T_CRM
+# define T_CRM "crmd"
+# endif
+
+# ifndef T_ATTRD
+# define T_ATTRD "attrd"
+# endif
+
+# define CIB_OPTIONS_FIRST "cib-bootstrap-options"
+
+# define F_CRM_DATA "crm_xml"
+# define F_CRM_TASK "crm_task"
+# define F_CRM_HOST_TO "crm_host_to"
+# define F_CRM_MSG_TYPE F_SUBTYPE
+# define F_CRM_SYS_TO "crm_sys_to"
+# define F_CRM_SYS_FROM "crm_sys_from"
+# define F_CRM_HOST_FROM F_ORIG
+# define F_CRM_REFERENCE XML_ATTR_REFERENCE
+# define F_CRM_VERSION XML_ATTR_VERSION
+# define F_CRM_ORIGIN "origin"
+# define F_CRM_USER "crm_user"
+# define F_CRM_JOIN_ID "join_id"
+# define F_CRM_DC_LEAVING "dc-leaving"
+# define F_CRM_ELECTION_ID "election-id"
+# define F_CRM_ELECTION_AGE_S "election-age-sec"
+# define F_CRM_ELECTION_AGE_US "election-age-nano-sec"
+# define F_CRM_ELECTION_OWNER "election-owner"
+# define F_CRM_TGRAPH "crm-tgraph-file"
+# define F_CRM_TGRAPH_INPUT "crm-tgraph-in"
+
+# define F_CRM_THROTTLE_MODE "crm-limit-mode"
+# define F_CRM_THROTTLE_MAX "crm-limit-max"
+
+/*---- Common tags/attrs */
+# define XML_DIFF_MARKER "__crm_diff_marker__"
+# define XML_TAG_CIB "cib"
+# define XML_TAG_FAILED "failed"
+
+# define XML_ATTR_CRM_VERSION "crm_feature_set"
+# define XML_ATTR_DIGEST "digest"
+# define XML_ATTR_VALIDATION "validate-with"
+
+# define XML_ATTR_QUORUM_PANIC "no-quorum-panic"
+# define XML_ATTR_HAVE_QUORUM "have-quorum"
+# define XML_ATTR_HAVE_WATCHDOG "have-watchdog"
+# define XML_ATTR_GENERATION "epoch"
+# define XML_ATTR_GENERATION_ADMIN "admin_epoch"
+# define XML_ATTR_NUMUPDATES "num_updates"
+# define XML_ATTR_TIMEOUT "timeout"
+# define XML_ATTR_ORIGIN "crm-debug-origin"
+# define XML_ATTR_TSTAMP "crm-timestamp"
+# define XML_CIB_ATTR_WRITTEN "cib-last-written"
+# define XML_ATTR_VERSION "version"
+# define XML_ATTR_DESC "description"
+# define XML_ATTR_ID "id"
+# define XML_ATTR_NAME "name"
+# define XML_ATTR_IDREF "id-ref"
+# define XML_ATTR_ID_LONG "long-id"
+# define XML_ATTR_TYPE "type"
+# define XML_ATTR_VERBOSE "verbose"
+# define XML_ATTR_OP "op"
+# define XML_ATTR_DC_UUID "dc-uuid"
+# define XML_ATTR_UPDATE_ORIG "update-origin"
+# define XML_ATTR_UPDATE_CLIENT "update-client"
+# define XML_ATTR_UPDATE_USER "update-user"
+
+# define XML_BOOLEAN_TRUE "true"
+# define XML_BOOLEAN_FALSE "false"
+# define XML_BOOLEAN_YES XML_BOOLEAN_TRUE
+# define XML_BOOLEAN_NO XML_BOOLEAN_FALSE
+
+# define XML_TAG_OPTIONS "options"
+
+/*---- top level tags/attrs */
+# define XML_ATTR_REQUEST "request"
+# define XML_ATTR_RESPONSE "response"
+
+# define XML_ATTR_UNAME "uname"
+# define XML_ATTR_REFERENCE "reference"
+
+# define XML_CRM_TAG_PING "ping_response"
+# define XML_PING_ATTR_STATUS "result"
+# define XML_PING_ATTR_SYSFROM "crm_subsystem"
+# define XML_PING_ATTR_CRMDSTATE "crmd_state"
+# define XML_PING_ATTR_PACEMAKERDSTATE "pacemakerd_state"
+# define XML_PING_ATTR_PACEMAKERDSTATE_INIT "init"
+# define XML_PING_ATTR_PACEMAKERDSTATE_STARTINGDAEMONS "starting_daemons"
+# define XML_PING_ATTR_PACEMAKERDSTATE_WAITPING "wait_for_ping"
+# define XML_PING_ATTR_PACEMAKERDSTATE_RUNNING "running"
+# define XML_PING_ATTR_PACEMAKERDSTATE_SHUTTINGDOWN "shutting_down"
+# define XML_PING_ATTR_PACEMAKERDSTATE_SHUTDOWNCOMPLETE "shutdown_complete"
+# define XML_PING_ATTR_PACEMAKERDSTATE_REMOTE "remote"
+
+# define XML_TAG_FRAGMENT "cib_fragment"
+
+# define XML_FAIL_TAG_CIB "failed_update"
+
+# define XML_FAILCIB_ATTR_ID "id"
+# define XML_FAILCIB_ATTR_OBJTYPE "object_type"
+# define XML_FAILCIB_ATTR_OP "operation"
+# define XML_FAILCIB_ATTR_REASON "reason"
+
+/*---- CIB specific tags/attrs */
+# define XML_CIB_TAG_SECTION_ALL "all"
+# define XML_CIB_TAG_CONFIGURATION "configuration"
+# define XML_CIB_TAG_STATUS "status"
+# define XML_CIB_TAG_RESOURCES "resources"
+# define XML_CIB_TAG_NODES "nodes"
+# define XML_CIB_TAG_DOMAINS "domains"
+# define XML_CIB_TAG_CONSTRAINTS "constraints"
+# define XML_CIB_TAG_CRMCONFIG "crm_config"
+# define XML_CIB_TAG_OPCONFIG "op_defaults"
+# define XML_CIB_TAG_RSCCONFIG "rsc_defaults"
+# define XML_CIB_TAG_ACLS "acls"
+# define XML_CIB_TAG_ALERTS "alerts"
+# define XML_CIB_TAG_ALERT "alert"
+# define XML_CIB_TAG_ALERT_RECIPIENT "recipient"
+# define XML_CIB_TAG_ALERT_SELECT "select"
+# define XML_CIB_TAG_ALERT_ATTRIBUTES "select_attributes"
+# define XML_CIB_TAG_ALERT_FENCING "select_fencing"
+# define XML_CIB_TAG_ALERT_NODES "select_nodes"
+# define XML_CIB_TAG_ALERT_RESOURCES "select_resources"
+# define XML_CIB_TAG_ALERT_ATTR "attribute"
+
+# define XML_CIB_TAG_STATE "node_state"
+# define XML_CIB_TAG_NODE "node"
+# define XML_CIB_TAG_NVPAIR "nvpair"
+
+# define XML_CIB_TAG_PROPSET "cluster_property_set"
+# define XML_TAG_ATTR_SETS "instance_attributes"
+# define XML_TAG_META_SETS "meta_attributes"
+# define XML_TAG_ATTRS "attributes"
+# define XML_TAG_PARAMS "parameters"
+# define XML_TAG_PARAM "param"
+# define XML_TAG_UTILIZATION "utilization"
+
+# define XML_TAG_RESOURCE_REF "resource_ref"
+# define XML_CIB_TAG_RESOURCE "primitive"
+# define XML_CIB_TAG_GROUP "group"
+# define XML_CIB_TAG_INCARNATION "clone"
+# define XML_CIB_TAG_CONTAINER "bundle"
+
+# define XML_CIB_TAG_RSC_TEMPLATE "template"
+
+# define XML_RSC_ATTR_TARGET "container-attribute-target"
+# define XML_RSC_ATTR_RESTART "restart-type"
+# define XML_RSC_ATTR_ORDERED "ordered"
+# define XML_RSC_ATTR_INTERLEAVE "interleave"
+# define XML_RSC_ATTR_INCARNATION "clone"
+# define XML_RSC_ATTR_INCARNATION_MAX "clone-max"
+# define XML_RSC_ATTR_INCARNATION_MIN "clone-min"
+# define XML_RSC_ATTR_INCARNATION_NODEMAX "clone-node-max"
+# define XML_RSC_ATTR_PROMOTABLE "promotable"
+# define XML_RSC_ATTR_PROMOTED_MAX "promoted-max"
+# define XML_RSC_ATTR_PROMOTED_NODEMAX "promoted-node-max"
+# define XML_RSC_ATTR_MANAGED "is-managed"
+# define XML_RSC_ATTR_TARGET_ROLE "target-role"
+# define XML_RSC_ATTR_UNIQUE "globally-unique"
+# define XML_RSC_ATTR_NOTIFY "notify"
+# define XML_RSC_ATTR_STICKINESS "resource-stickiness"
+# define XML_RSC_ATTR_FAIL_STICKINESS "migration-threshold"
+# define XML_RSC_ATTR_FAIL_TIMEOUT "failure-timeout"
+# define XML_RSC_ATTR_MULTIPLE "multiple-active"
+# define XML_RSC_ATTR_REQUIRES "requires"
+# define XML_RSC_ATTR_CONTAINER "container"
+# define XML_RSC_ATTR_INTERNAL_RSC "internal_rsc"
+# define XML_RSC_ATTR_MAINTENANCE "maintenance"
+# define XML_RSC_ATTR_REMOTE_NODE "remote-node"
+# define XML_RSC_ATTR_CLEAR_OP "clear_failure_op"
+# define XML_RSC_ATTR_CLEAR_INTERVAL "clear_failure_interval"
+# define XML_RSC_ATTR_REMOTE_RA_ADDR "addr"
+# define XML_RSC_ATTR_REMOTE_RA_SERVER "server"
+# define XML_RSC_ATTR_REMOTE_RA_PORT "port"
+# define XML_RSC_ATTR_CRITICAL "critical"
+
+# define XML_REMOTE_ATTR_RECONNECT_INTERVAL "reconnect_interval"
+
+# define XML_OP_ATTR_ON_FAIL "on-fail"
+# define XML_OP_ATTR_START_DELAY "start-delay"
+# define XML_OP_ATTR_ALLOW_MIGRATE "allow-migrate"
+# define XML_OP_ATTR_ORIGIN "interval-origin"
+# define XML_OP_ATTR_PENDING "record-pending"
+# define XML_OP_ATTR_DIGESTS_ALL "digests-all"
+# define XML_OP_ATTR_DIGESTS_SECURE "digests-secure"
+
+# define XML_CIB_TAG_LRM "lrm"
+# define XML_LRM_TAG_RESOURCES "lrm_resources"
+# define XML_LRM_TAG_RESOURCE "lrm_resource"
+# define XML_LRM_TAG_RSC_OP "lrm_rsc_op"
+# define XML_AGENT_ATTR_CLASS "class"
+# define XML_AGENT_ATTR_PROVIDER "provider"
+
+//! \deprecated Do not use (will be removed in a future release)
+# define XML_CIB_ATTR_REPLACE "replace"
+
+# define XML_CIB_ATTR_SOURCE "source"
+
+# define XML_CIB_ATTR_PRIORITY "priority"
+# define XML_CIB_ATTR_SOURCE "source"
+
+# define XML_NODE_JOIN_STATE "join"
+# define XML_NODE_EXPECTED "expected"
+# define XML_NODE_IN_CLUSTER "in_ccm"
+# define XML_NODE_IS_PEER "crmd"
+# define XML_NODE_IS_REMOTE "remote_node"
+# define XML_NODE_IS_FENCED "node_fenced"
+# define XML_NODE_IS_MAINTENANCE "node_in_maintenance"
+
+# define XML_CIB_ATTR_SHUTDOWN "shutdown"
+
+/* Aside from being an old name for the executor, LRM is a misnomer here because
+ * the controller and scheduler use these to track actions, which are not always
+ * executor operations.
+ */
+
+// XML attribute that takes interval specification (user-facing configuration)
+# define XML_LRM_ATTR_INTERVAL "interval"
+
+// XML attribute that takes interval in milliseconds (daemon APIs)
+// (identical value as above, but different constant allows clearer code intent)
+# define XML_LRM_ATTR_INTERVAL_MS XML_LRM_ATTR_INTERVAL
+
+# define XML_LRM_ATTR_TASK "operation"
+# define XML_LRM_ATTR_TASK_KEY "operation_key"
+# define XML_LRM_ATTR_TARGET "on_node"
+# define XML_LRM_ATTR_TARGET_UUID "on_node_uuid"
+/*! Actions to be executed on Pacemaker Remote nodes are routed through the
+ * controller on the cluster node hosting the remote connection. That cluster
+ * node is considered the router node for the action.
+ */
+# define XML_LRM_ATTR_ROUTER_NODE "router_node"
+# define XML_LRM_ATTR_RSCID "rsc-id"
+# define XML_LRM_ATTR_OPSTATUS "op-status"
+# define XML_LRM_ATTR_RC "rc-code"
+# define XML_LRM_ATTR_CALLID "call-id"
+# define XML_LRM_ATTR_OP_DIGEST "op-digest"
+# define XML_LRM_ATTR_OP_RESTART "op-force-restart"
+# define XML_LRM_ATTR_OP_SECURE "op-secure-params"
+# define XML_LRM_ATTR_RESTART_DIGEST "op-restart-digest"
+# define XML_LRM_ATTR_SECURE_DIGEST "op-secure-digest"
+# define XML_LRM_ATTR_EXIT_REASON "exit-reason"
+
+# define XML_RSC_OP_LAST_CHANGE "last-rc-change"
+# define XML_RSC_OP_LAST_RUN "last-run" // deprecated since 2.0.3
+# define XML_RSC_OP_T_EXEC "exec-time"
+# define XML_RSC_OP_T_QUEUE "queue-time"
+
+# define XML_LRM_ATTR_MIGRATE_SOURCE "migrate_source"
+# define XML_LRM_ATTR_MIGRATE_TARGET "migrate_target"
+
+# define XML_TAG_GRAPH "transition_graph"
+# define XML_GRAPH_TAG_RSC_OP "rsc_op"
+# define XML_GRAPH_TAG_PSEUDO_EVENT "pseudo_event"
+# define XML_GRAPH_TAG_CRM_EVENT "crm_event"
+# define XML_GRAPH_TAG_DOWNED "downed"
+# define XML_GRAPH_TAG_MAINTENANCE "maintenance"
+
+# define XML_TAG_RULE "rule"
+# define XML_RULE_ATTR_SCORE "score"
+# define XML_RULE_ATTR_SCORE_ATTRIBUTE "score-attribute"
+# define XML_RULE_ATTR_ROLE "role"
+# define XML_RULE_ATTR_BOOLEAN_OP "boolean-op"
+
+# define XML_TAG_EXPRESSION "expression"
+# define XML_EXPR_ATTR_ATTRIBUTE "attribute"
+# define XML_EXPR_ATTR_OPERATION "operation"
+# define XML_EXPR_ATTR_VALUE "value"
+# define XML_EXPR_ATTR_TYPE "type"
+# define XML_EXPR_ATTR_VALUE_SOURCE "value-source"
+
+# define XML_CONS_TAG_RSC_DEPEND "rsc_colocation"
+# define XML_CONS_TAG_RSC_ORDER "rsc_order"
+# define XML_CONS_TAG_RSC_LOCATION "rsc_location"
+# define XML_CONS_TAG_RSC_TICKET "rsc_ticket"
+# define XML_CONS_TAG_RSC_SET "resource_set"
+# define XML_CONS_ATTR_SYMMETRICAL "symmetrical"
+
+# define XML_LOCATION_ATTR_DISCOVERY "resource-discovery"
+
+# define XML_COLOC_ATTR_SOURCE "rsc"
+# define XML_COLOC_ATTR_SOURCE_ROLE "rsc-role"
+# define XML_COLOC_ATTR_TARGET "with-rsc"
+# define XML_COLOC_ATTR_TARGET_ROLE "with-rsc-role"
+# define XML_COLOC_ATTR_NODE_ATTR "node-attribute"
+# define XML_COLOC_ATTR_INFLUENCE "influence"
+
+//! \deprecated Deprecated since 2.1.5
+# define XML_COLOC_ATTR_SOURCE_INSTANCE "rsc-instance"
+
+//! \deprecated Deprecated since 2.1.5
+# define XML_COLOC_ATTR_TARGET_INSTANCE "with-rsc-instance"
+
+# define XML_LOC_ATTR_SOURCE "rsc"
+# define XML_LOC_ATTR_SOURCE_PATTERN "rsc-pattern"
+
+# define XML_ORDER_ATTR_FIRST "first"
+# define XML_ORDER_ATTR_THEN "then"
+# define XML_ORDER_ATTR_FIRST_ACTION "first-action"
+# define XML_ORDER_ATTR_THEN_ACTION "then-action"
+# define XML_ORDER_ATTR_KIND "kind"
+
+//! \deprecated Deprecated since 2.1.5
+# define XML_ORDER_ATTR_FIRST_INSTANCE "first-instance"
+
+//! \deprecated Deprecated since 2.1.5
+# define XML_ORDER_ATTR_THEN_INSTANCE "then-instance"
+
+# define XML_TICKET_ATTR_TICKET "ticket"
+# define XML_TICKET_ATTR_LOSS_POLICY "loss-policy"
+
+# define XML_NVPAIR_ATTR_NAME "name"
+# define XML_NVPAIR_ATTR_VALUE "value"
+
+# define XML_NODE_ATTR_RSC_DISCOVERY "resource-discovery-enabled"
+
+# define XML_CONFIG_ATTR_DC_DEADTIME "dc-deadtime"
+# define XML_CONFIG_ATTR_ELECTION_FAIL "election-timeout"
+# define XML_CONFIG_ATTR_FORCE_QUIT "shutdown-escalation"
+# define XML_CONFIG_ATTR_RECHECK "cluster-recheck-interval"
+# define XML_CONFIG_ATTR_FENCE_REACTION "fence-reaction"
+# define XML_CONFIG_ATTR_SHUTDOWN_LOCK "shutdown-lock"
+# define XML_CONFIG_ATTR_SHUTDOWN_LOCK_LIMIT "shutdown-lock-limit"
+# define XML_CONFIG_ATTR_PRIORITY_FENCING_DELAY "priority-fencing-delay"
+
+# define XML_ALERT_ATTR_PATH "path"
+# define XML_ALERT_ATTR_TIMEOUT "timeout"
+# define XML_ALERT_ATTR_TSTAMP_FORMAT "timestamp-format"
+# define XML_ALERT_ATTR_REC_VALUE "value"
+
+# define XML_CIB_TAG_GENERATION_TUPPLE "generation_tuple"
+
+# define XML_ATTR_TRANSITION_MAGIC "transition-magic"
+# define XML_ATTR_TRANSITION_KEY "transition-key"
+
+# define XML_ATTR_TE_NOWAIT "op_no_wait"
+# define XML_ATTR_TE_TARGET_RC "op_target_rc"
+# define XML_TAG_TRANSIENT_NODEATTRS "transient_attributes"
+
+# define XML_TAG_DIFF_ADDED "diff-added"
+# define XML_TAG_DIFF_REMOVED "diff-removed"
+
+# define XML_ACL_TAG_USER "acl_target"
+# define XML_ACL_TAG_USERv1 "acl_user"
+# define XML_ACL_TAG_GROUP "acl_group"
+# define XML_ACL_TAG_ROLE "acl_role"
+# define XML_ACL_TAG_PERMISSION "acl_permission"
+# define XML_ACL_TAG_ROLE_REF "role"
+# define XML_ACL_TAG_ROLE_REFv1 "role_ref"
+# define XML_ACL_ATTR_KIND "kind"
+# define XML_ACL_TAG_READ "read"
+# define XML_ACL_TAG_WRITE "write"
+# define XML_ACL_TAG_DENY "deny"
+# define XML_ACL_ATTR_REF "reference"
+# define XML_ACL_ATTR_REFv1 "ref"
+# define XML_ACL_ATTR_TAG "object-type"
+# define XML_ACL_ATTR_TAGv1 "tag"
+# define XML_ACL_ATTR_XPATH "xpath"
+# define XML_ACL_ATTR_ATTRIBUTE "attribute"
+
+# define XML_CIB_TAG_TICKETS "tickets"
+# define XML_CIB_TAG_TICKET_STATE "ticket_state"
+
+# define XML_CIB_TAG_TAGS "tags"
+# define XML_CIB_TAG_TAG "tag"
+# define XML_CIB_TAG_OBJ_REF "obj_ref"
+
+# define XML_TAG_FENCING_TOPOLOGY "fencing-topology"
+# define XML_TAG_FENCING_LEVEL "fencing-level"
+# define XML_ATTR_STONITH_INDEX "index"
+# define XML_ATTR_STONITH_TARGET "target"
+# define XML_ATTR_STONITH_TARGET_VALUE "target-value"
+# define XML_ATTR_STONITH_TARGET_PATTERN "target-pattern"
+# define XML_ATTR_STONITH_TARGET_ATTRIBUTE "target-attribute"
+# define XML_ATTR_STONITH_DEVICES "devices"
+
+# define XML_TAG_DIFF "diff"
+# define XML_DIFF_VERSION "version"
+# define XML_DIFF_VSOURCE "source"
+# define XML_DIFF_VTARGET "target"
+# define XML_DIFF_CHANGE "change"
+# define XML_DIFF_LIST "change-list"
+# define XML_DIFF_ATTR "change-attr"
+# define XML_DIFF_RESULT "change-result"
+# define XML_DIFF_OP "operation"
+# define XML_DIFF_PATH "path"
+# define XML_DIFF_POSITION "position"
+
+# define ID(x) crm_element_value(x, XML_ATTR_ID)
+# define TYPE(x) crm_element_name(x)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/crm/msg_xml_compat.h b/include/crm/msg_xml_compat.h
new file mode 100644
index 0000000..aad98e8
--- /dev/null
+++ b/include/crm/msg_xml_compat.h
@@ -0,0 +1,65 @@
+/*
+ * 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 PCMK__CRM_MSG_XML_COMPAT__H
+# define PCMK__CRM_MSG_XML_COMPAT__H
+
+#include <crm/common/agents.h> // PCMK_STONITH_PROVIDES
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * \file
+ * \brief Deprecated Pacemaker XML constants API
+ * \ingroup core
+ * \deprecated Do not include this header directly. The XML constants in this
+ * header, and the header itself, will be removed in a future
+ * release.
+ */
+
+//! \deprecated Use PCMK_STONITH_PROVIDES instead
+#define XML_RSC_ATTR_PROVIDES PCMK_STONITH_PROVIDES
+
+//! \deprecated Use PCMK_XE_PROMOTABLE_LEGACY instead
+#define XML_CIB_TAG_MASTER PCMK_XE_PROMOTABLE_LEGACY
+
+//! \deprecated Use PCMK_XA_PROMOTED_MAX_LEGACY instead
+#define PCMK_XE_PROMOTED_MAX_LEGACY PCMK_XA_PROMOTED_MAX_LEGACY
+
+//! \deprecated Use PCMK_XA_PROMOTED_MAX_LEGACY instead
+#define XML_RSC_ATTR_MASTER_MAX PCMK_XA_PROMOTED_MAX_LEGACY
+
+//! \deprecated Use PCMK_XA_PROMOTED_NODE_MAX_LEGACY instead
+#define PCMK_XE_PROMOTED_NODE_MAX_LEGACY PCMK_XA_PROMOTED_NODE_MAX_LEGACY
+
+//! \deprecated Use PCMK_XA_PROMOTED_NODE_MAX_LEGACY instead
+#define XML_RSC_ATTR_MASTER_NODEMAX PCMK_XA_PROMOTED_NODE_MAX_LEGACY
+
+//! \deprecated Do not use (will be removed in a future release)
+#define XML_ATTR_RA_VERSION "ra-version"
+
+//! \deprecated Do not use (will be removed in a future release)
+#define XML_TAG_RSC_VER_ATTRS "rsc_versioned_attrs"
+
+//! \deprecated Do not use (will be removed in a future release)
+#define XML_TAG_OP_VER_ATTRS "op_versioned_attrs"
+
+//! \deprecated Do not use (will be removed in a future release)
+#define XML_TAG_OP_VER_META "op_versioned_meta"
+
+//! \deprecated Use \p XML_ATTR_ID instead
+#define XML_ATTR_UUID "id"
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // PCMK__CRM_MSG_XML_COMPAT__H
diff --git a/include/crm/pengine/Makefile.am b/include/crm/pengine/Makefile.am
new file mode 100644
index 0000000..fac6031
--- /dev/null
+++ b/include/crm/pengine/Makefile.am
@@ -0,0 +1,17 @@
+#
+# Copyright 2006-2021 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.
+#
+MAINTAINERCLEANFILES = Makefile.in
+
+headerdir=$(pkgincludedir)/crm/pengine
+
+noinst_HEADERS = internal.h remote_internal.h rules_internal.h
+header_HEADERS = common.h complex.h pe_types.h rules.h status.h \
+ common_compat.h \
+ pe_types_compat.h \
+ rules_compat.h
diff --git a/include/crm/pengine/common.h b/include/crm/pengine/common.h
new file mode 100644
index 0000000..9fe05bd
--- /dev/null
+++ b/include/crm/pengine/common.h
@@ -0,0 +1,209 @@
+/*
+ * Copyright 2004-2023 the Pacemaker project contributors
+ *
+ * The version control history for this file may have further details.
+ *
+ * This source code is licensed under the GNU Lesser General Public License
+ * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
+ */
+
+#ifndef PCMK__CRM_PENGINE_COMMON__H
+# define PCMK__CRM_PENGINE_COMMON__H
+
+# include <glib.h>
+# include <regex.h>
+# include <crm/common/iso8601.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern gboolean was_processing_error;
+extern gboolean was_processing_warning;
+
+/* The order is (partially) significant here; the values from action_fail_ignore
+ * through action_fail_fence are in order of increasing severity.
+ *
+ * @COMPAT The values should be ordered and numbered per the "TODO" comments
+ * below, so all values are in order of severity and there is room for
+ * future additions, but that would break API compatibility.
+ * @TODO For now, we just use a function to compare the values specially, but
+ * at the next compatibility break, we should arrange things properly.
+ */
+enum action_fail_response {
+ action_fail_ignore, // @TODO = 10
+ // @TODO action_fail_demote = 20,
+ action_fail_recover, // @TODO = 30
+ // @TODO action_fail_reset_remote = 40,
+ // @TODO action_fail_restart_container = 50,
+ action_fail_migrate, // @TODO = 60
+ action_fail_block, // @TODO = 70
+ action_fail_stop, // @TODO = 80
+ action_fail_standby, // @TODO = 90
+ action_fail_fence, // @TODO = 100
+
+ // @COMPAT Values below here are out of order for API compatibility
+
+ action_fail_restart_container,
+
+ /* This is reserved for internal use for remote node connection resources.
+ * Fence the remote node if stonith is enabled, otherwise attempt to recover
+ * the connection resource. This allows us to specify types of connection
+ * resource failures that should result in fencing the remote node
+ * (for example, recurring monitor failures).
+ */
+ action_fail_reset_remote,
+
+ action_fail_demote,
+};
+
+/* the "done" action must be the "pre" action +1 */
+enum action_tasks {
+ no_action,
+ monitor_rsc,
+ stop_rsc,
+ stopped_rsc,
+ start_rsc,
+ started_rsc,
+ action_notify,
+ action_notified,
+ action_promote,
+ action_promoted,
+ action_demote,
+ action_demoted,
+ shutdown_crm,
+ stonith_node
+};
+
+enum rsc_recovery_type {
+ recovery_stop_start,
+ recovery_stop_only,
+ recovery_block,
+ recovery_stop_unexpected,
+};
+
+enum rsc_start_requirement {
+ rsc_req_nothing, /* Allowed by custom_action() */
+ rsc_req_quorum, /* Enforced by custom_action() */
+ rsc_req_stonith /* Enforced by native_start_constraints() */
+};
+
+//! Possible roles that a resource can be in
+enum rsc_role_e {
+ RSC_ROLE_UNKNOWN = 0,
+ RSC_ROLE_STOPPED = 1,
+ RSC_ROLE_STARTED = 2,
+ RSC_ROLE_UNPROMOTED = 3,
+ RSC_ROLE_PROMOTED = 4,
+
+#if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1)
+ //! \deprecated Use RSC_ROLE_UNPROMOTED instead
+ RSC_ROLE_SLAVE = RSC_ROLE_UNPROMOTED,
+
+ //! \deprecated Use RSC_ROLE_PROMOTED instead
+ RSC_ROLE_MASTER = RSC_ROLE_PROMOTED,
+#endif
+};
+
+# define RSC_ROLE_MAX (RSC_ROLE_PROMOTED + 1)
+
+# define RSC_ROLE_UNKNOWN_S "Unknown"
+# define RSC_ROLE_STOPPED_S "Stopped"
+# define RSC_ROLE_STARTED_S "Started"
+# define RSC_ROLE_UNPROMOTED_S "Unpromoted"
+# define RSC_ROLE_PROMOTED_S "Promoted"
+# define RSC_ROLE_UNPROMOTED_LEGACY_S "Slave"
+# define RSC_ROLE_PROMOTED_LEGACY_S "Master"
+
+//! Deprecated
+enum pe_print_options {
+ pe_print_log = (1 << 0),
+ pe_print_html = (1 << 1),
+ pe_print_ncurses = (1 << 2),
+ pe_print_printf = (1 << 3),
+ pe_print_dev = (1 << 4), //! Ignored
+ pe_print_details = (1 << 5), //! Ignored
+ pe_print_max_details = (1 << 6), //! Ignored
+ pe_print_rsconly = (1 << 7),
+ pe_print_ops = (1 << 8),
+ pe_print_suppres_nl = (1 << 9),
+ pe_print_xml = (1 << 10),
+ pe_print_brief = (1 << 11),
+ pe_print_pending = (1 << 12),
+ pe_print_clone_details = (1 << 13),
+ pe_print_clone_active = (1 << 14), // Print clone instances only if active
+ pe_print_implicit = (1 << 15) // Print implicitly created resources
+};
+
+const char *task2text(enum action_tasks task);
+enum action_tasks text2task(const char *task);
+enum rsc_role_e text2role(const char *role);
+const char *role2text(enum rsc_role_e role);
+const char *fail2text(enum action_fail_response fail);
+
+const char *pe_pref(GHashTable * options, const char *name);
+
+/*!
+ * \brief Get readable description of a recovery type
+ *
+ * \param[in] type Recovery type
+ *
+ * \return Static string describing \p type
+ */
+static inline const char *
+recovery2text(enum rsc_recovery_type type)
+{
+ switch (type) {
+ case recovery_stop_only:
+ return "shutting it down";
+ case recovery_stop_start:
+ return "attempting recovery";
+ case recovery_block:
+ return "waiting for an administrator";
+ case recovery_stop_unexpected:
+ return "stopping unexpected instances";
+ }
+ return "Unknown";
+}
+
+typedef struct pe_re_match_data {
+ char *string;
+ int nregs;
+ regmatch_t *pmatch;
+} pe_re_match_data_t;
+
+typedef struct pe_match_data {
+ pe_re_match_data_t *re;
+ GHashTable *params;
+ GHashTable *meta;
+} pe_match_data_t;
+
+typedef struct pe_rsc_eval_data {
+ const char *standard;
+ const char *provider;
+ const char *agent;
+} pe_rsc_eval_data_t;
+
+typedef struct pe_op_eval_data {
+ const char *op_name;
+ guint interval;
+} pe_op_eval_data_t;
+
+typedef struct pe_rule_eval_data {
+ GHashTable *node_hash; // Only used with g_hash_table_lookup()
+ enum rsc_role_e role;
+ crm_time_t *now; // @COMPAT could be const
+ pe_match_data_t *match_data; // @COMPAT could be const
+ pe_rsc_eval_data_t *rsc_data; // @COMPAT could be const
+ pe_op_eval_data_t *op_data; // @COMPAT could be const
+} pe_rule_eval_data_t;
+
+#if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1)
+#include <crm/pengine/common_compat.h>
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/crm/pengine/common_compat.h b/include/crm/pengine/common_compat.h
new file mode 100644
index 0000000..773bb3d
--- /dev/null
+++ b/include/crm/pengine/common_compat.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2004-2021 the Pacemaker project contributors
+ *
+ * The version control history for this file may have further details.
+ *
+ * This source code is licensed under the GNU Lesser General Public License
+ * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
+ */
+
+#ifndef PCMK__CRM_PENGINE_COMMON_COMPAT__H
+# define PCMK__CRM_PENGINE_COMMON_COMPAT__H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * \file
+ * \brief Deprecated Pacemaker scheduler utilities
+ * \ingroup pengine
+ * \deprecated Do not include this header directly. The utilities in this
+ * header, and the header itself, will be removed in a future
+ * release.
+ */
+
+//! \deprecated Use RSC_ROLE_UNPROMOTED_LEGACY_S instead
+# define RSC_ROLE_SLAVE_S RSC_ROLE_UNPROMOTED_LEGACY_S
+
+//! \deprecated Use RSC_ROLE_PROMOTED_LEGACY_S instead
+# define RSC_ROLE_MASTER_S RSC_ROLE_PROMOTED_LEGACY_S
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // PCMK__CRM_PENGINE_COMMON_COMPAT__H
diff --git a/include/crm/pengine/complex.h b/include/crm/pengine/complex.h
new file mode 100644
index 0000000..929e4da
--- /dev/null
+++ b/include/crm/pengine/complex.h
@@ -0,0 +1,37 @@
+/*
+ * 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 PCMK__CRM_PENGINE_COMPLEX__H
+# define PCMK__CRM_PENGINE_COMPLEX__H
+
+#include <glib.h> // gboolean, GHashTable
+#include <libxml/tree.h> // xmlNode
+#include <crm/pengine/pe_types.h> // pe_node_t, pe_resource_t, etc.
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern resource_object_functions_t resource_class_functions[];
+
+GHashTable *pe_rsc_params(pe_resource_t *rsc, const pe_node_t *node,
+ pe_working_set_t *data_set);
+void get_meta_attributes(GHashTable * meta_hash, pe_resource_t *rsc,
+ pe_node_t *node, pe_working_set_t *data_set);
+void get_rsc_attributes(GHashTable *meta_hash, const pe_resource_t *rsc,
+ const pe_node_t *node, pe_working_set_t *data_set);
+
+gboolean is_parent(pe_resource_t *child, pe_resource_t *rsc);
+pe_resource_t *uber_parent(pe_resource_t *rsc);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/crm/pengine/internal.h b/include/crm/pengine/internal.h
new file mode 100644
index 0000000..1b5f6f1
--- /dev/null
+++ b/include/crm/pengine/internal.h
@@ -0,0 +1,724 @@
+/*
+ * Copyright 2004-2023 the Pacemaker project contributors
+ *
+ * The version control history for this file may have further details.
+ *
+ * This source code is licensed under the GNU Lesser General Public License
+ * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
+ */
+
+#ifndef PE_INTERNAL__H
+# define PE_INTERNAL__H
+
+# include <stdint.h>
+# include <string.h>
+# include <crm/msg_xml.h>
+# include <crm/pengine/status.h>
+# include <crm/pengine/remote_internal.h>
+# include <crm/common/internal.h>
+# include <crm/common/options_internal.h>
+# include <crm/common/output_internal.h>
+
+const char *pe__resource_description(const pe_resource_t *rsc, uint32_t show_opts);
+
+enum pe__clone_flags {
+ // Whether instances should be started sequentially
+ pe__clone_ordered = (1 << 0),
+
+ // Whether promotion scores have been added
+ pe__clone_promotion_added = (1 << 1),
+
+ // Whether promotion constraints have been added
+ pe__clone_promotion_constrained = (1 << 2),
+};
+
+bool pe__clone_is_ordered(const pe_resource_t *clone);
+int pe__set_clone_flag(pe_resource_t *clone, enum pe__clone_flags flag);
+
+
+enum pe__group_flags {
+ pe__group_ordered = (1 << 0), // Members start sequentially
+ pe__group_colocated = (1 << 1), // Members must be on same node
+};
+
+bool pe__group_flag_is_set(const pe_resource_t *group, uint32_t flags);
+pe_resource_t *pe__last_group_member(const pe_resource_t *group);
+
+
+# define pe_rsc_info(rsc, fmt, args...) crm_log_tag(LOG_INFO, rsc ? rsc->id : "<NULL>", fmt, ##args)
+# define pe_rsc_debug(rsc, fmt, args...) crm_log_tag(LOG_DEBUG, rsc ? rsc->id : "<NULL>", fmt, ##args)
+# define pe_rsc_trace(rsc, fmt, args...) crm_log_tag(LOG_TRACE, rsc ? rsc->id : "<NULL>", fmt, ##args)
+
+# define pe_err(fmt...) do { \
+ was_processing_error = TRUE; \
+ pcmk__config_err(fmt); \
+ } while (0)
+
+# define pe_warn(fmt...) do { \
+ was_processing_warning = TRUE; \
+ pcmk__config_warn(fmt); \
+ } while (0)
+
+# define pe_proc_err(fmt...) { was_processing_error = TRUE; crm_err(fmt); }
+# define pe_proc_warn(fmt...) { was_processing_warning = TRUE; crm_warn(fmt); }
+
+#define pe__set_working_set_flags(working_set, flags_to_set) do { \
+ (working_set)->flags = pcmk__set_flags_as(__func__, __LINE__, \
+ LOG_TRACE, "Working set", crm_system_name, \
+ (working_set)->flags, (flags_to_set), #flags_to_set); \
+ } while (0)
+
+#define pe__clear_working_set_flags(working_set, flags_to_clear) do { \
+ (working_set)->flags = pcmk__clear_flags_as(__func__, __LINE__, \
+ LOG_TRACE, "Working set", crm_system_name, \
+ (working_set)->flags, (flags_to_clear), #flags_to_clear); \
+ } while (0)
+
+#define pe__set_resource_flags(resource, flags_to_set) do { \
+ (resource)->flags = pcmk__set_flags_as(__func__, __LINE__, \
+ LOG_TRACE, "Resource", (resource)->id, (resource)->flags, \
+ (flags_to_set), #flags_to_set); \
+ } while (0)
+
+#define pe__clear_resource_flags(resource, flags_to_clear) do { \
+ (resource)->flags = pcmk__clear_flags_as(__func__, __LINE__, \
+ LOG_TRACE, "Resource", (resource)->id, (resource)->flags, \
+ (flags_to_clear), #flags_to_clear); \
+ } while (0)
+
+#define pe__set_action_flags(action, flags_to_set) do { \
+ (action)->flags = pcmk__set_flags_as(__func__, __LINE__, \
+ LOG_TRACE, \
+ "Action", (action)->uuid, \
+ (action)->flags, \
+ (flags_to_set), \
+ #flags_to_set); \
+ } while (0)
+
+#define pe__clear_action_flags(action, flags_to_clear) do { \
+ (action)->flags = pcmk__clear_flags_as(__func__, __LINE__, \
+ LOG_TRACE, \
+ "Action", (action)->uuid, \
+ (action)->flags, \
+ (flags_to_clear), \
+ #flags_to_clear); \
+ } while (0)
+
+#define pe__set_raw_action_flags(action_flags, action_name, flags_to_set) do { \
+ action_flags = pcmk__set_flags_as(__func__, __LINE__, \
+ LOG_TRACE, "Action", action_name, \
+ (action_flags), \
+ (flags_to_set), #flags_to_set); \
+ } while (0)
+
+#define pe__clear_raw_action_flags(action_flags, action_name, flags_to_clear) do { \
+ action_flags = pcmk__clear_flags_as(__func__, __LINE__, \
+ LOG_TRACE, \
+ "Action", action_name, \
+ (action_flags), \
+ (flags_to_clear), \
+ #flags_to_clear); \
+ } while (0)
+
+#define pe__set_action_flags_as(function, line, action, flags_to_set) do { \
+ (action)->flags = pcmk__set_flags_as((function), (line), \
+ LOG_TRACE, \
+ "Action", (action)->uuid, \
+ (action)->flags, \
+ (flags_to_set), \
+ #flags_to_set); \
+ } while (0)
+
+#define pe__clear_action_flags_as(function, line, action, flags_to_clear) do { \
+ (action)->flags = pcmk__clear_flags_as((function), (line), \
+ LOG_TRACE, \
+ "Action", (action)->uuid, \
+ (action)->flags, \
+ (flags_to_clear), \
+ #flags_to_clear); \
+ } while (0)
+
+#define pe__set_order_flags(order_flags, flags_to_set) do { \
+ order_flags = pcmk__set_flags_as(__func__, __LINE__, LOG_TRACE, \
+ "Ordering", "constraint", \
+ order_flags, (flags_to_set), \
+ #flags_to_set); \
+ } while (0)
+
+#define pe__clear_order_flags(order_flags, flags_to_clear) do { \
+ order_flags = pcmk__clear_flags_as(__func__, __LINE__, LOG_TRACE, \
+ "Ordering", "constraint", \
+ order_flags, (flags_to_clear), \
+ #flags_to_clear); \
+ } while (0)
+
+// Some warnings we don't want to print every transition
+
+enum pe_warn_once_e {
+ pe_wo_blind = (1 << 0),
+ pe_wo_restart_type = (1 << 1),
+ pe_wo_role_after = (1 << 2),
+ pe_wo_poweroff = (1 << 3),
+ pe_wo_require_all = (1 << 4),
+ pe_wo_order_score = (1 << 5),
+ pe_wo_neg_threshold = (1 << 6),
+ pe_wo_remove_after = (1 << 7),
+ pe_wo_ping_node = (1 << 8),
+ pe_wo_order_inst = (1 << 9),
+ pe_wo_coloc_inst = (1 << 10),
+ pe_wo_group_order = (1 << 11),
+ pe_wo_group_coloc = (1 << 12),
+ pe_wo_upstart = (1 << 13),
+ pe_wo_nagios = (1 << 14),
+};
+
+extern uint32_t pe_wo;
+
+#define pe_warn_once(pe_wo_bit, fmt...) do { \
+ if (!pcmk_is_set(pe_wo, pe_wo_bit)) { \
+ if (pe_wo_bit == pe_wo_blind) { \
+ crm_warn(fmt); \
+ } else { \
+ pe_warn(fmt); \
+ } \
+ pe_wo = pcmk__set_flags_as(__func__, __LINE__, LOG_TRACE, \
+ "Warn-once", "logging", pe_wo, \
+ (pe_wo_bit), #pe_wo_bit); \
+ } \
+ } while (0);
+
+
+typedef struct pe__location_constraint_s {
+ char *id; // Constraint XML ID
+ pe_resource_t *rsc_lh; // Resource being located
+ enum rsc_role_e role_filter; // Role to locate
+ enum pe_discover_e discover_mode; // Resource discovery
+ GList *node_list_rh; // List of pe_node_t*
+} pe__location_t;
+
+typedef struct pe__order_constraint_s {
+ int id;
+ uint32_t flags; // Group of enum pe_ordering flags
+
+ void *lh_opaque;
+ pe_resource_t *lh_rsc;
+ pe_action_t *lh_action;
+ char *lh_action_task;
+
+ void *rh_opaque;
+ pe_resource_t *rh_rsc;
+ pe_action_t *rh_action;
+ char *rh_action_task;
+} pe__ordering_t;
+
+const pe_resource_t *pe__const_top_resource(const pe_resource_t *rsc,
+ bool include_bundle);
+
+int pe__clone_max(const pe_resource_t *clone);
+int pe__clone_node_max(const pe_resource_t *clone);
+int pe__clone_promoted_max(const pe_resource_t *clone);
+int pe__clone_promoted_node_max(const pe_resource_t *clone);
+void pe__create_clone_notifications(pe_resource_t *clone);
+void pe__free_clone_notification_data(pe_resource_t *clone);
+void pe__create_clone_notif_pseudo_ops(pe_resource_t *clone,
+ pe_action_t *start, pe_action_t *started,
+ pe_action_t *stop, pe_action_t *stopped);
+
+
+pe_action_t *pe__new_rsc_pseudo_action(pe_resource_t *rsc, const char *task,
+ bool optional, bool runnable);
+
+void pe__create_promotable_pseudo_ops(pe_resource_t *clone, bool any_promoting,
+ bool any_demoting);
+
+bool pe_can_fence(const pe_working_set_t *data_set, const pe_node_t *node);
+
+void add_hash_param(GHashTable * hash, const char *name, const char *value);
+
+char *native_parameter(pe_resource_t * rsc, pe_node_t * node, gboolean create, const char *name,
+ pe_working_set_t * data_set);
+pe_node_t *native_location(const pe_resource_t *rsc, GList **list, int current);
+
+void pe_metadata(pcmk__output_t *out);
+void verify_pe_options(GHashTable * options);
+
+void native_add_running(pe_resource_t * rsc, pe_node_t * node, pe_working_set_t * data_set, gboolean failed);
+
+gboolean native_unpack(pe_resource_t * rsc, pe_working_set_t * data_set);
+gboolean group_unpack(pe_resource_t * rsc, pe_working_set_t * data_set);
+gboolean clone_unpack(pe_resource_t * rsc, pe_working_set_t * data_set);
+gboolean pe__unpack_bundle(pe_resource_t *rsc, pe_working_set_t *data_set);
+
+pe_resource_t *native_find_rsc(pe_resource_t *rsc, const char *id, const pe_node_t *node,
+ int flags);
+
+gboolean native_active(pe_resource_t * rsc, gboolean all);
+gboolean group_active(pe_resource_t * rsc, gboolean all);
+gboolean clone_active(pe_resource_t * rsc, gboolean all);
+gboolean pe__bundle_active(pe_resource_t *rsc, gboolean all);
+
+//! \deprecated This function will be removed in a future release
+void native_print(pe_resource_t *rsc, const char *pre_text, long options,
+ void *print_data);
+
+//! \deprecated This function will be removed in a future release
+void group_print(pe_resource_t *rsc, const char *pre_text, long options,
+ void *print_data);
+
+//! \deprecated This function will be removed in a future release
+void clone_print(pe_resource_t *rsc, const char *pre_text, long options,
+ void *print_data);
+
+//! \deprecated This function will be removed in a future release
+void pe__print_bundle(pe_resource_t *rsc, const char *pre_text, long options,
+ void *print_data);
+
+gchar *pcmk__native_output_string(const pe_resource_t *rsc, const char *name,
+ const pe_node_t *node, uint32_t show_opts,
+ const char *target_role, bool show_nodes);
+
+int pe__name_and_nvpairs_xml(pcmk__output_t *out, bool is_list, const char *tag_name
+ , size_t pairs_count, ...);
+char *pe__node_display_name(pe_node_t *node, bool print_detail);
+
+
+// Clone notifications (pe_notif.c)
+void pe__order_notifs_after_fencing(const pe_action_t *action,
+ pe_resource_t *rsc,
+ pe_action_t *stonith_op);
+
+
+static inline const char *
+pe__rsc_bool_str(const pe_resource_t *rsc, uint64_t rsc_flag)
+{
+ return pcmk__btoa(pcmk_is_set(rsc->flags, rsc_flag));
+}
+
+int pe__clone_xml(pcmk__output_t *out, va_list args);
+int pe__clone_default(pcmk__output_t *out, va_list args);
+int pe__group_xml(pcmk__output_t *out, va_list args);
+int pe__group_default(pcmk__output_t *out, va_list args);
+int pe__bundle_xml(pcmk__output_t *out, va_list args);
+int pe__bundle_html(pcmk__output_t *out, va_list args);
+int pe__bundle_text(pcmk__output_t *out, va_list args);
+int pe__node_html(pcmk__output_t *out, va_list args);
+int pe__node_text(pcmk__output_t *out, va_list args);
+int pe__node_xml(pcmk__output_t *out, va_list args);
+int pe__resource_xml(pcmk__output_t *out, va_list args);
+int pe__resource_html(pcmk__output_t *out, va_list args);
+int pe__resource_text(pcmk__output_t *out, va_list args);
+
+void native_free(pe_resource_t * rsc);
+void group_free(pe_resource_t * rsc);
+void clone_free(pe_resource_t * rsc);
+void pe__free_bundle(pe_resource_t *rsc);
+
+enum rsc_role_e native_resource_state(const pe_resource_t * rsc, gboolean current);
+enum rsc_role_e group_resource_state(const pe_resource_t * rsc, gboolean current);
+enum rsc_role_e clone_resource_state(const pe_resource_t * rsc, gboolean current);
+enum rsc_role_e pe__bundle_resource_state(const pe_resource_t *rsc,
+ gboolean current);
+
+void pe__count_common(pe_resource_t *rsc);
+void pe__count_bundle(pe_resource_t *rsc);
+
+void common_free(pe_resource_t * rsc);
+
+pe_node_t *pe__copy_node(const pe_node_t *this_node);
+extern time_t get_effective_time(pe_working_set_t * data_set);
+
+/* Failure handling utilities (from failcounts.c) */
+
+// bit flags for fail count handling options
+enum pe_fc_flags_e {
+ pe_fc_default = (1 << 0),
+ pe_fc_effective = (1 << 1), // don't count expired failures
+ pe_fc_fillers = (1 << 2), // if container, include filler failures in count
+};
+
+int pe_get_failcount(const pe_node_t *node, pe_resource_t *rsc,
+ time_t *last_failure, uint32_t flags,
+ const xmlNode *xml_op);
+
+pe_action_t *pe__clear_failcount(pe_resource_t *rsc, const pe_node_t *node,
+ const char *reason,
+ pe_working_set_t *data_set);
+
+/* Functions for finding/counting a resource's active nodes */
+
+bool pe__count_active_node(const pe_resource_t *rsc, pe_node_t *node,
+ pe_node_t **active, unsigned int *count_all,
+ unsigned int *count_clean);
+
+pe_node_t *pe__find_active_requires(const pe_resource_t *rsc,
+ unsigned int *count);
+
+static inline pe_node_t *
+pe__current_node(const pe_resource_t *rsc)
+{
+ return (rsc == NULL)? NULL : rsc->fns->active_node(rsc, NULL, NULL);
+}
+
+
+/* Binary like operators for lists of nodes */
+extern void node_list_exclude(GHashTable * list, GList *list2, gboolean merge_scores);
+
+GHashTable *pe__node_list2table(const GList *list);
+
+static inline gpointer
+pe_hash_table_lookup(GHashTable * hash, gconstpointer key)
+{
+ if (hash) {
+ return g_hash_table_lookup(hash, key);
+ }
+ return NULL;
+}
+
+extern pe_action_t *get_pseudo_op(const char *name, pe_working_set_t * data_set);
+extern gboolean order_actions(pe_action_t * lh_action, pe_action_t * rh_action, enum pe_ordering order);
+
+void pe__show_node_weights_as(const char *file, const char *function,
+ int line, bool to_log, const pe_resource_t *rsc,
+ const char *comment, GHashTable *nodes,
+ pe_working_set_t *data_set);
+
+#define pe__show_node_weights(level, rsc, text, nodes, data_set) \
+ pe__show_node_weights_as(__FILE__, __func__, __LINE__, \
+ (level), (rsc), (text), (nodes), (data_set))
+
+xmlNode *find_rsc_op_entry(const pe_resource_t *rsc, const char *key);
+
+pe_action_t *custom_action(pe_resource_t *rsc, char *key, const char *task,
+ const pe_node_t *on_node, gboolean optional,
+ gboolean foo, pe_working_set_t *data_set);
+
+# define delete_key(rsc) pcmk__op_key(rsc->id, CRMD_ACTION_DELETE, 0)
+# define delete_action(rsc, node, optional) custom_action( \
+ rsc, delete_key(rsc), CRMD_ACTION_DELETE, node, \
+ optional, TRUE, rsc->cluster);
+
+# define stopped_key(rsc) pcmk__op_key(rsc->id, CRMD_ACTION_STOPPED, 0)
+# define stopped_action(rsc, node, optional) custom_action( \
+ rsc, stopped_key(rsc), CRMD_ACTION_STOPPED, node, \
+ optional, TRUE, rsc->cluster);
+
+# define stop_key(rsc) pcmk__op_key(rsc->id, CRMD_ACTION_STOP, 0)
+# define stop_action(rsc, node, optional) custom_action( \
+ rsc, stop_key(rsc), CRMD_ACTION_STOP, node, \
+ optional, TRUE, rsc->cluster);
+
+# define reload_key(rsc) pcmk__op_key(rsc->id, CRMD_ACTION_RELOAD_AGENT, 0)
+# define start_key(rsc) pcmk__op_key(rsc->id, CRMD_ACTION_START, 0)
+# define start_action(rsc, node, optional) custom_action( \
+ rsc, start_key(rsc), CRMD_ACTION_START, node, \
+ optional, TRUE, rsc->cluster)
+
+# define started_key(rsc) pcmk__op_key(rsc->id, CRMD_ACTION_STARTED, 0)
+# define started_action(rsc, node, optional) custom_action( \
+ rsc, started_key(rsc), CRMD_ACTION_STARTED, node, \
+ optional, TRUE, rsc->cluster)
+
+# define promote_key(rsc) pcmk__op_key(rsc->id, CRMD_ACTION_PROMOTE, 0)
+# define promote_action(rsc, node, optional) custom_action( \
+ rsc, promote_key(rsc), CRMD_ACTION_PROMOTE, node, \
+ optional, TRUE, rsc->cluster)
+
+# define promoted_key(rsc) pcmk__op_key(rsc->id, CRMD_ACTION_PROMOTED, 0)
+# define promoted_action(rsc, node, optional) custom_action( \
+ rsc, promoted_key(rsc), CRMD_ACTION_PROMOTED, node, \
+ optional, TRUE, rsc->cluster)
+
+# define demote_key(rsc) pcmk__op_key(rsc->id, CRMD_ACTION_DEMOTE, 0)
+# define demote_action(rsc, node, optional) custom_action( \
+ rsc, demote_key(rsc), CRMD_ACTION_DEMOTE, node, \
+ optional, TRUE, rsc->cluster)
+
+# define demoted_key(rsc) pcmk__op_key(rsc->id, CRMD_ACTION_DEMOTED, 0)
+# define demoted_action(rsc, node, optional) custom_action( \
+ rsc, demoted_key(rsc), CRMD_ACTION_DEMOTED, node, \
+ optional, TRUE, rsc->cluster)
+
+extern int pe_get_configured_timeout(pe_resource_t *rsc, const char *action,
+ pe_working_set_t *data_set);
+
+pe_action_t *find_first_action(const GList *input, const char *uuid,
+ const char *task, const pe_node_t *on_node);
+
+enum action_tasks get_complex_task(const pe_resource_t *rsc, const char *name);
+
+extern GList *find_actions(GList *input, const char *key, const pe_node_t *on_node);
+GList *find_actions_exact(GList *input, const char *key,
+ const pe_node_t *on_node);
+GList *pe__resource_actions(const pe_resource_t *rsc, const pe_node_t *node,
+ const char *task, bool require_node);
+
+extern void pe_free_action(pe_action_t * action);
+
+void resource_location(pe_resource_t *rsc, const pe_node_t *node, int score,
+ const char *tag, pe_working_set_t *data_set);
+
+extern int pe__is_newer_op(const xmlNode *xml_a, const xmlNode *xml_b,
+ bool same_node_default);
+extern gint sort_op_by_callid(gconstpointer a, gconstpointer b);
+gboolean get_target_role(const pe_resource_t *rsc, enum rsc_role_e *role);
+void pe__set_next_role(pe_resource_t *rsc, enum rsc_role_e role,
+ const char *why);
+
+pe_resource_t *find_clone_instance(const pe_resource_t *rsc,
+ const char *sub_id);
+
+extern void destroy_ticket(gpointer data);
+extern pe_ticket_t *ticket_new(const char *ticket_id, pe_working_set_t * data_set);
+
+// Resources for manipulating resource names
+const char *pe_base_name_end(const char *id);
+char *clone_strip(const char *last_rsc_id);
+char *clone_zero(const char *last_rsc_id);
+
+static inline bool
+pe_base_name_eq(const pe_resource_t *rsc, const char *id)
+{
+ if (id && rsc && rsc->id) {
+ // Number of characters in rsc->id before any clone suffix
+ size_t base_len = pe_base_name_end(rsc->id) - rsc->id + 1;
+
+ return (strlen(id) == base_len) && !strncmp(id, rsc->id, base_len);
+ }
+ return false;
+}
+
+int pe__target_rc_from_xml(const xmlNode *xml_op);
+
+gint pe__cmp_node_name(gconstpointer a, gconstpointer b);
+bool is_set_recursive(const pe_resource_t *rsc, long long flag, bool any);
+
+enum rsc_digest_cmp_val {
+ /*! Digests are the same */
+ RSC_DIGEST_MATCH = 0,
+ /*! Params that require a restart changed */
+ RSC_DIGEST_RESTART,
+ /*! Some parameter changed. */
+ RSC_DIGEST_ALL,
+ /*! rsc op didn't have a digest associated with it, so
+ * it is unknown if parameters changed or not. */
+ RSC_DIGEST_UNKNOWN,
+};
+
+typedef struct op_digest_cache_s {
+ enum rsc_digest_cmp_val rc;
+ xmlNode *params_all;
+ xmlNode *params_secure;
+ xmlNode *params_restart;
+ char *digest_all_calc;
+ char *digest_secure_calc;
+ char *digest_restart_calc;
+} op_digest_cache_t;
+
+op_digest_cache_t *pe__calculate_digests(pe_resource_t *rsc, const char *task,
+ guint *interval_ms,
+ const pe_node_t *node,
+ const xmlNode *xml_op,
+ GHashTable *overrides,
+ bool calc_secure,
+ pe_working_set_t *data_set);
+
+void pe__free_digests(gpointer ptr);
+
+op_digest_cache_t *rsc_action_digest_cmp(pe_resource_t *rsc,
+ const xmlNode *xml_op,
+ pe_node_t *node,
+ pe_working_set_t *data_set);
+
+pe_action_t *pe_fence_op(pe_node_t *node, const char *op, bool optional,
+ const char *reason, bool priority_delay,
+ pe_working_set_t *data_set);
+void trigger_unfencing(pe_resource_t *rsc, pe_node_t *node,
+ const char *reason, pe_action_t *dependency,
+ pe_working_set_t *data_set);
+
+char *pe__action2reason(const pe_action_t *action, enum pe_action_flags flag);
+void pe_action_set_reason(pe_action_t *action, const char *reason, bool overwrite);
+void pe__add_action_expected_result(pe_action_t *action, int expected_result);
+
+void pe__set_resource_flags_recursive(pe_resource_t *rsc, uint64_t flags);
+void pe__clear_resource_flags_recursive(pe_resource_t *rsc, uint64_t flags);
+void pe__clear_resource_flags_on_all(pe_working_set_t *data_set, uint64_t flag);
+
+gboolean add_tag_ref(GHashTable * tags, const char * tag_name, const char * obj_ref);
+
+//! \deprecated This function will be removed in a future release
+void print_rscs_brief(GList *rsc_list, const char * pre_text, long options,
+ void * print_data, gboolean print_all);
+int pe__rscs_brief_output(pcmk__output_t *out, GList *rsc_list, unsigned int options);
+void pe_fence_node(pe_working_set_t * data_set, pe_node_t * node, const char *reason, bool priority_delay);
+
+pe_node_t *pe_create_node(const char *id, const char *uname, const char *type,
+ const char *score, pe_working_set_t * data_set);
+
+//! \deprecated This function will be removed in a future release
+void common_print(pe_resource_t *rsc, const char *pre_text, const char *name,
+ const pe_node_t *node, long options, void *print_data);
+int pe__common_output_text(pcmk__output_t *out, const pe_resource_t *rsc,
+ const char *name, const pe_node_t *node,
+ unsigned int options);
+int pe__common_output_html(pcmk__output_t *out, const pe_resource_t *rsc,
+ const char *name, const pe_node_t *node,
+ unsigned int options);
+
+GList *pe__bundle_containers(const pe_resource_t *bundle);
+
+int pe__bundle_max(const pe_resource_t *rsc);
+int pe__bundle_max_per_node(const pe_resource_t *rsc);
+
+pe_resource_t *pe__find_bundle_replica(const pe_resource_t *bundle,
+ const pe_node_t *node);
+bool pe__bundle_needs_remote_name(pe_resource_t *rsc);
+const char *pe__add_bundle_remote_name(pe_resource_t *rsc,
+ pe_working_set_t *data_set,
+ xmlNode *xml, const char *field);
+const char *pe_node_attribute_calculated(const pe_node_t *node,
+ const char *name,
+ const pe_resource_t *rsc);
+const char *pe_node_attribute_raw(const pe_node_t *node, const char *name);
+bool pe__is_universal_clone(const pe_resource_t *rsc,
+ const pe_working_set_t *data_set);
+void pe__add_param_check(const xmlNode *rsc_op, pe_resource_t *rsc,
+ pe_node_t *node, enum pe_check_parameters,
+ pe_working_set_t *data_set);
+void pe__foreach_param_check(pe_working_set_t *data_set,
+ void (*cb)(pe_resource_t*, pe_node_t*,
+ const xmlNode*,
+ enum pe_check_parameters));
+void pe__free_param_checks(pe_working_set_t *data_set);
+
+bool pe__shutdown_requested(const pe_node_t *node);
+void pe__update_recheck_time(time_t recheck, pe_working_set_t *data_set);
+
+/*!
+ * \internal
+ * \brief Register xml formatting message functions.
+ *
+ * \param[in,out] out Output object to register messages with
+ */
+void pe__register_messages(pcmk__output_t *out);
+
+void pe__unpack_dataset_nvpairs(const xmlNode *xml_obj, const char *set_name,
+ const pe_rule_eval_data_t *rule_data,
+ GHashTable *hash, const char *always_first,
+ gboolean overwrite, pe_working_set_t *data_set);
+
+bool pe__resource_is_disabled(const pe_resource_t *rsc);
+pe_action_t *pe__clear_resource_history(pe_resource_t *rsc,
+ const pe_node_t *node,
+ pe_working_set_t *data_set);
+
+GList *pe__rscs_with_tag(pe_working_set_t *data_set, const char *tag_name);
+GList *pe__unames_with_tag(pe_working_set_t *data_set, const char *tag_name);
+bool pe__rsc_has_tag(pe_working_set_t *data_set, const char *rsc, const char *tag);
+bool pe__uname_has_tag(pe_working_set_t *data_set, const char *node, const char *tag);
+
+bool pe__rsc_running_on_only(const pe_resource_t *rsc, const pe_node_t *node);
+bool pe__rsc_running_on_any(pe_resource_t *rsc, GList *node_list);
+GList *pe__filter_rsc_list(GList *rscs, GList *filter);
+GList * pe__build_node_name_list(pe_working_set_t *data_set, const char *s);
+GList * pe__build_rsc_list(pe_working_set_t *data_set, const char *s);
+
+bool pcmk__rsc_filtered_by_node(pe_resource_t *rsc, GList *only_node);
+
+gboolean pe__bundle_is_filtered(const pe_resource_t *rsc, GList *only_rsc,
+ gboolean check_parent);
+gboolean pe__clone_is_filtered(const pe_resource_t *rsc, GList *only_rsc,
+ gboolean check_parent);
+gboolean pe__group_is_filtered(const pe_resource_t *rsc, GList *only_rsc,
+ gboolean check_parent);
+gboolean pe__native_is_filtered(const pe_resource_t *rsc, GList *only_rsc,
+ gboolean check_parent);
+
+xmlNode *pe__failed_probe_for_rsc(const pe_resource_t *rsc, const char *name);
+
+const char *pe__clone_child_id(const pe_resource_t *rsc);
+
+int pe__sum_node_health_scores(const pe_node_t *node, int base_health);
+int pe__node_health(pe_node_t *node);
+
+static inline enum pcmk__health_strategy
+pe__health_strategy(pe_working_set_t *data_set)
+{
+ return pcmk__parse_health_strategy(pe_pref(data_set->config_hash,
+ PCMK__OPT_NODE_HEALTH_STRATEGY));
+}
+
+static inline int
+pe__health_score(const char *option, pe_working_set_t *data_set)
+{
+ return char2score(pe_pref(data_set->config_hash, option));
+}
+
+/*!
+ * \internal
+ * \brief Return a string suitable for logging as a node name
+ *
+ * \param[in] node Node to return a node name string for
+ *
+ * \return Node name if available, otherwise node ID if available,
+ * otherwise "unspecified node" if node is NULL or "unidentified node"
+ * if node has neither a name nor ID.
+ */
+static inline const char *
+pe__node_name(const pe_node_t *node)
+{
+ if (node == NULL) {
+ return "unspecified node";
+
+ } else if (node->details->uname != NULL) {
+ return node->details->uname;
+
+ } else if (node->details->id != NULL) {
+ return node->details->id;
+
+ } else {
+ return "unidentified node";
+ }
+}
+
+/*!
+ * \internal
+ * \brief Check whether two node objects refer to the same node
+ *
+ * \param[in] node1 First node object to compare
+ * \param[in] node2 Second node object to compare
+ *
+ * \return true if \p node1 and \p node2 refer to the same node
+ */
+static inline bool
+pe__same_node(const pe_node_t *node1, const pe_node_t *node2)
+{
+ return (node1 != NULL) && (node2 != NULL)
+ && (node1->details == node2->details);
+}
+
+/*!
+ * \internal
+ * \brief Get the operation key from an action history entry
+ *
+ * \param[in] xml Action history entry
+ *
+ * \return Entry's operation key
+ */
+static inline const char *
+pe__xe_history_key(const xmlNode *xml)
+{
+ if (xml == NULL) {
+ return NULL;
+ } else {
+ /* @COMPAT Pacemaker <= 1.1.5 did not add the key, and used the ID
+ * instead. Checking for that allows us to process old saved CIBs,
+ * including some regression tests.
+ */
+ const char *key = crm_element_value(xml, XML_LRM_ATTR_TASK_KEY);
+
+ return pcmk__str_empty(key)? ID(xml) : key;
+ }
+}
+
+#endif
diff --git a/include/crm/pengine/pe_types.h b/include/crm/pengine/pe_types.h
new file mode 100644
index 0000000..cc626c8
--- /dev/null
+++ b/include/crm/pengine/pe_types.h
@@ -0,0 +1,568 @@
+/*
+ * Copyright 2004-2023 the Pacemaker project contributors
+ *
+ * The version control history for this file may have further details.
+ *
+ * This source code is licensed under the GNU Lesser General Public License
+ * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
+ */
+
+#ifndef PCMK__CRM_PENGINE_PE_TYPES__H
+# define PCMK__CRM_PENGINE_PE_TYPES__H
+
+
+# include <stdbool.h> // bool
+# include <sys/types.h> // time_t
+# include <libxml/tree.h> // xmlNode
+# include <glib.h> // gboolean, guint, GList, GHashTable
+# include <crm/common/iso8601.h>
+# include <crm/pengine/common.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*!
+ * \file
+ * \brief Data types for cluster status
+ * \ingroup pengine
+ */
+
+typedef struct pe_node_s pe_node_t;
+typedef struct pe_action_s pe_action_t;
+typedef struct pe_resource_s pe_resource_t;
+typedef struct pe_working_set_s pe_working_set_t;
+
+enum pe_obj_types {
+ pe_unknown = -1,
+ pe_native = 0,
+ pe_group = 1,
+ pe_clone = 2,
+ pe_container = 3,
+};
+
+typedef struct resource_object_functions_s {
+ gboolean (*unpack) (pe_resource_t*, pe_working_set_t*);
+ pe_resource_t *(*find_rsc) (pe_resource_t *parent, const char *search,
+ const pe_node_t *node, int flags);
+ /* parameter result must be free'd */
+ char *(*parameter) (pe_resource_t*, pe_node_t*, gboolean, const char*,
+ pe_working_set_t*);
+ //! \deprecated will be removed in a future release
+ void (*print) (pe_resource_t*, const char*, long, void*);
+ gboolean (*active) (pe_resource_t*, gboolean);
+ enum rsc_role_e (*state) (const pe_resource_t*, gboolean);
+ pe_node_t *(*location) (const pe_resource_t*, GList**, int);
+ void (*free) (pe_resource_t*);
+ void (*count) (pe_resource_t*);
+ gboolean (*is_filtered) (const pe_resource_t*, GList *, gboolean);
+
+ /*!
+ * \brief
+ * \internal Find a node (and optionally count all) where resource is active
+ *
+ * \param[in] rsc Resource to check
+ * \param[out] count_all If not NULL, set this to count of active nodes
+ * \param[out] count_clean If not NULL, set this to count of clean nodes
+ *
+ * \return A node where the resource is active, preferring the source node
+ * if the resource is involved in a partial migration or a clean,
+ * online node if the resource's "requires" is "quorum" or
+ * "nothing", or NULL if the resource is inactive.
+ */
+ pe_node_t *(*active_node)(const pe_resource_t *rsc, unsigned int *count_all,
+ unsigned int *count_clean);
+} resource_object_functions_t;
+
+typedef struct resource_alloc_functions_s resource_alloc_functions_t;
+
+enum pe_quorum_policy {
+ no_quorum_freeze,
+ no_quorum_stop,
+ no_quorum_ignore,
+ no_quorum_suicide,
+ no_quorum_demote
+};
+
+enum node_type {
+ node_ping, //! \deprecated Do not use
+ node_member,
+ node_remote
+};
+
+//! \deprecated will be removed in a future release
+enum pe_restart {
+ pe_restart_restart, //! \deprecated will be removed in a future release
+ pe_restart_ignore //! \deprecated will be removed in a future release
+};
+
+//! Determine behavior of pe_find_resource_with_flags()
+enum pe_find {
+ pe_find_renamed = 0x001, //!< match resource ID or LRM history ID
+ pe_find_anon = 0x002, //!< match base name of anonymous clone instances
+ pe_find_clone = 0x004, //!< match only clone instances
+ pe_find_current = 0x008, //!< match resource active on specified node
+ pe_find_inactive = 0x010, //!< match resource not running anywhere
+ pe_find_any = 0x020, //!< match base name of any clone instance
+};
+
+// @TODO Make these an enum
+
+# define pe_flag_have_quorum 0x00000001ULL
+# define pe_flag_symmetric_cluster 0x00000002ULL
+# define pe_flag_maintenance_mode 0x00000008ULL
+
+# define pe_flag_stonith_enabled 0x00000010ULL
+# define pe_flag_have_stonith_resource 0x00000020ULL
+# define pe_flag_enable_unfencing 0x00000040ULL
+# define pe_flag_concurrent_fencing 0x00000080ULL
+
+# define pe_flag_stop_rsc_orphans 0x00000100ULL
+# define pe_flag_stop_action_orphans 0x00000200ULL
+# define pe_flag_stop_everything 0x00000400ULL
+
+# define pe_flag_start_failure_fatal 0x00001000ULL
+
+//! \deprecated
+# define pe_flag_remove_after_stop 0x00002000ULL
+
+# define pe_flag_startup_fencing 0x00004000ULL
+# define pe_flag_shutdown_lock 0x00008000ULL
+
+# define pe_flag_startup_probes 0x00010000ULL
+# define pe_flag_have_status 0x00020000ULL
+# define pe_flag_have_remote_nodes 0x00040000ULL
+
+# define pe_flag_quick_location 0x00100000ULL
+# define pe_flag_sanitized 0x00200000ULL
+
+//! \deprecated
+# define pe_flag_stdout 0x00400000ULL
+
+//! Don't count total, disabled and blocked resource instances
+# define pe_flag_no_counts 0x00800000ULL
+
+/*! Skip deprecated code that is kept solely for backward API compatibility.
+ * (Internal code should always set this.)
+ */
+# define pe_flag_no_compat 0x01000000ULL
+
+# define pe_flag_show_scores 0x02000000ULL
+# define pe_flag_show_utilization 0x04000000ULL
+
+/*!
+ * When scheduling, only unpack the CIB (including constraints), calculate
+ * as much cluster status as possible, and apply node health.
+ */
+# define pe_flag_check_config 0x08000000ULL
+
+struct pe_working_set_s {
+ xmlNode *input;
+ crm_time_t *now;
+
+ /* options extracted from the input */
+ char *dc_uuid;
+ pe_node_t *dc_node;
+ const char *stonith_action;
+ const char *placement_strategy;
+
+ unsigned long long flags;
+
+ int stonith_timeout;
+ enum pe_quorum_policy no_quorum_policy;
+
+ GHashTable *config_hash;
+ GHashTable *tickets;
+
+ // Actions for which there can be only one (e.g. fence nodeX)
+ GHashTable *singletons;
+
+ GList *nodes;
+ GList *resources;
+ GList *placement_constraints;
+ GList *ordering_constraints;
+ GList *colocation_constraints;
+ GList *ticket_constraints;
+
+ GList *actions;
+ xmlNode *failed;
+ xmlNode *op_defaults;
+ xmlNode *rsc_defaults;
+
+ /* stats */
+ int num_synapse;
+ int max_valid_nodes; //! Deprecated (will be removed in a future release)
+ int order_id;
+ int action_id;
+
+ /* final output */
+ xmlNode *graph;
+
+ GHashTable *template_rsc_sets;
+ const char *localhost;
+ GHashTable *tags;
+
+ int blocked_resources;
+ int disabled_resources;
+
+ GList *param_check; // History entries that need to be checked
+ GList *stop_needed; // Containers that need stop actions
+ time_t recheck_by; // Hint to controller to re-run scheduler by this time
+ int ninstances; // Total number of resource instances
+ guint shutdown_lock;// How long (seconds) to lock resources to shutdown node
+ int priority_fencing_delay; // Priority fencing delay
+
+ void *priv;
+};
+
+enum pe_check_parameters {
+ /* Clear fail count if parameters changed for un-expired start or monitor
+ * last_failure.
+ */
+ pe_check_last_failure,
+
+ /* Clear fail count if parameters changed for start, monitor, promote, or
+ * migrate_from actions for active resources.
+ */
+ pe_check_active,
+};
+
+struct pe_node_shared_s {
+ const char *id;
+ const char *uname;
+ enum node_type type;
+
+ /* @TODO convert these flags into a bitfield */
+ gboolean online;
+ gboolean standby;
+ gboolean standby_onfail;
+ gboolean pending;
+ gboolean unclean;
+ gboolean unseen;
+ gboolean shutdown;
+ gboolean expected_up;
+ gboolean is_dc;
+ gboolean maintenance;
+ gboolean rsc_discovery_enabled;
+ gboolean remote_requires_reset;
+ gboolean remote_was_fenced;
+ gboolean remote_maintenance; /* what the remote-rsc is thinking */
+ gboolean unpacked;
+
+ int num_resources;
+ pe_resource_t *remote_rsc;
+ GList *running_rsc; /* pe_resource_t* */
+ GList *allocated_rsc; /* pe_resource_t* */
+
+ GHashTable *attrs; /* char* => char* */
+ GHashTable *utilization;
+ GHashTable *digest_cache; //!< cache of calculated resource digests
+ int priority; // calculated based on the priority of resources running on the node
+ pe_working_set_t *data_set; //!< Cluster that this node is part of
+};
+
+struct pe_node_s {
+ int weight;
+ gboolean fixed; //!< \deprecated Will be removed in a future release
+ int count;
+ struct pe_node_shared_s *details;
+ int rsc_discover_mode;
+};
+
+# define pe_rsc_orphan 0x00000001ULL
+# define pe_rsc_managed 0x00000002ULL
+# define pe_rsc_block 0x00000004ULL
+# define pe_rsc_orphan_container_filler 0x00000008ULL
+
+# define pe_rsc_notify 0x00000010ULL
+# define pe_rsc_unique 0x00000020ULL
+# define pe_rsc_fence_device 0x00000040ULL
+# define pe_rsc_promotable 0x00000080ULL
+
+# define pe_rsc_provisional 0x00000100ULL
+# define pe_rsc_allocating 0x00000200ULL
+# define pe_rsc_merging 0x00000400ULL
+# define pe_rsc_restarting 0x00000800ULL
+
+# define pe_rsc_stop 0x00001000ULL
+# define pe_rsc_reload 0x00002000ULL
+# define pe_rsc_allow_remote_remotes 0x00004000ULL
+# define pe_rsc_critical 0x00008000ULL
+
+# define pe_rsc_failed 0x00010000ULL
+# define pe_rsc_detect_loop 0x00020000ULL
+# define pe_rsc_runnable 0x00040000ULL
+# define pe_rsc_start_pending 0x00080000ULL
+
+//!< \deprecated Do not use
+# define pe_rsc_starting 0x00100000ULL
+
+//!< \deprecated Do not use
+# define pe_rsc_stopping 0x00200000ULL
+
+# define pe_rsc_stop_unexpected 0x00400000ULL
+# define pe_rsc_allow_migrate 0x00800000ULL
+
+# define pe_rsc_failure_ignored 0x01000000ULL
+# define pe_rsc_replica_container 0x02000000ULL
+# define pe_rsc_maintenance 0x04000000ULL
+# define pe_rsc_is_container 0x08000000ULL
+
+# define pe_rsc_needs_quorum 0x10000000ULL
+# define pe_rsc_needs_fencing 0x20000000ULL
+# define pe_rsc_needs_unfencing 0x40000000ULL
+
+/* *INDENT-OFF* */
+enum pe_action_flags {
+ pe_action_pseudo = 0x00001,
+ pe_action_runnable = 0x00002,
+ pe_action_optional = 0x00004,
+ pe_action_print_always = 0x00008,
+
+ pe_action_have_node_attrs = 0x00010,
+ pe_action_implied_by_stonith = 0x00040,
+ pe_action_migrate_runnable = 0x00080,
+
+ pe_action_dumped = 0x00100,
+ pe_action_processed = 0x00200,
+#if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1)
+ pe_action_clear = 0x00400, //! \deprecated Unused
+#endif
+ pe_action_dangle = 0x00800,
+
+ /* This action requires one or more of its dependencies to be runnable.
+ * We use this to clear the runnable flag before checking dependencies.
+ */
+ pe_action_requires_any = 0x01000,
+
+ pe_action_reschedule = 0x02000,
+ pe_action_tracking = 0x04000,
+ pe_action_dedup = 0x08000, //! Internal state tracking when creating graph
+
+ pe_action_dc = 0x10000, //! Action may run on DC instead of target
+};
+/* *INDENT-ON* */
+
+struct pe_resource_s {
+ char *id;
+ char *clone_name;
+ xmlNode *xml;
+ xmlNode *orig_xml;
+ xmlNode *ops_xml;
+
+ pe_working_set_t *cluster;
+ pe_resource_t *parent;
+
+ enum pe_obj_types variant;
+ void *variant_opaque;
+ resource_object_functions_t *fns;
+ resource_alloc_functions_t *cmds;
+
+ enum rsc_recovery_type recovery_type;
+
+ enum pe_restart restart_type; //!< \deprecated will be removed in future release
+
+ int priority;
+ int stickiness;
+ int sort_index;
+ int failure_timeout;
+ int migration_threshold;
+ guint remote_reconnect_ms;
+ char *pending_task;
+
+ unsigned long long flags;
+
+ // @TODO merge these into flags
+ gboolean is_remote_node;
+ gboolean exclusive_discover;
+
+ /* Pay special attention to whether you want to use rsc_cons_lhs and
+ * rsc_cons directly, which include only colocations explicitly involving
+ * this resource, or call libpacemaker's pcmk__with_this_colocations() and
+ * pcmk__this_with_colocations() functions, which may return relevant
+ * colocations involving the resource's ancestors as well.
+ */
+
+ //!@{
+ //! This field should be treated as internal to Pacemaker
+ GList *rsc_cons_lhs; // List of pcmk__colocation_t*
+ GList *rsc_cons; // List of pcmk__colocation_t*
+ GList *rsc_location; // List of pe__location_t*
+ GList *actions; // List of pe_action_t*
+ GList *rsc_tickets; // List of rsc_ticket*
+ //!@}
+
+ pe_node_t *allocated_to;
+ pe_node_t *partial_migration_target;
+ pe_node_t *partial_migration_source;
+ GList *running_on; /* pe_node_t* */
+ GHashTable *known_on; /* pe_node_t* */
+ GHashTable *allowed_nodes; /* pe_node_t* */
+
+ enum rsc_role_e role;
+ enum rsc_role_e next_role;
+
+ GHashTable *meta;
+ GHashTable *parameters; //! \deprecated Use pe_rsc_params() instead
+ GHashTable *utilization;
+
+ GList *children; /* pe_resource_t* */
+ GList *dangling_migrations; /* pe_node_t* */
+
+ pe_resource_t *container;
+ GList *fillers;
+
+ // @COMPAT These should be made const at next API compatibility break
+ pe_node_t *pending_node; // Node on which pending_task is happening
+ pe_node_t *lock_node; // Resource is shutdown-locked to this node
+
+ time_t lock_time; // When shutdown lock started
+
+ /* Resource parameters may have node-attribute-based rules, which means the
+ * values can vary by node. This table is a cache of parameter name/value
+ * tables for each node (as needed). Use pe_rsc_params() to get the table
+ * for a given node.
+ */
+ GHashTable *parameter_cache; // Key = node name, value = parameters table
+};
+
+struct pe_action_s {
+ int id;
+ int priority;
+
+ pe_resource_t *rsc;
+ pe_node_t *node;
+ xmlNode *op_entry;
+
+ char *task;
+ char *uuid;
+ char *cancel_task;
+ char *reason;
+
+ enum pe_action_flags flags;
+ enum rsc_start_requirement needs;
+ enum action_fail_response on_fail;
+ enum rsc_role_e fail_role;
+
+ GHashTable *meta;
+ GHashTable *extra;
+
+ /*
+ * These two varables are associated with the constraint logic
+ * that involves first having one or more actions runnable before
+ * then allowing this action to execute.
+ *
+ * These varables are used with features such as 'clone-min' which
+ * requires at minimum X number of cloned instances to be running
+ * before an order dependency can run. Another option that uses
+ * this is 'require-all=false' in ordering constrants. This option
+ * says "only require one instance of a resource to start before
+ * allowing dependencies to start" -- basically, require-all=false is
+ * the same as clone-min=1.
+ */
+
+ /* current number of known runnable actions in the before list. */
+ int runnable_before;
+ /* the number of "before" runnable actions required for this action
+ * to be considered runnable */
+ int required_runnable_before;
+
+ GList *actions_before; /* pe_action_wrapper_t* */
+ GList *actions_after; /* pe_action_wrapper_t* */
+
+ /* Some of the above fields could be moved to the details,
+ * except for API backward compatibility.
+ */
+ void *action_details; // varies by type of action
+};
+
+typedef struct pe_ticket_s {
+ char *id;
+ gboolean granted;
+ time_t last_granted;
+ gboolean standby;
+ GHashTable *state;
+} pe_ticket_t;
+
+typedef struct pe_tag_s {
+ char *id;
+ GList *refs;
+} pe_tag_t;
+
+//! Internal tracking for transition graph creation
+enum pe_link_state {
+ pe_link_not_dumped, //! Internal tracking for transition graph creation
+ pe_link_dumped, //! Internal tracking for transition graph creation
+ pe_link_dup, //! \deprecated No longer used by Pacemaker
+};
+
+enum pe_discover_e {
+ pe_discover_always = 0,
+ pe_discover_never,
+ pe_discover_exclusive,
+};
+
+/* *INDENT-OFF* */
+enum pe_ordering {
+ pe_order_none = 0x0, /* deleted */
+ pe_order_optional = 0x1, /* pure ordering, nothing implied */
+ pe_order_apply_first_non_migratable = 0x2, /* Only apply this constraint's ordering if first is not migratable. */
+
+ pe_order_implies_first = 0x10, /* If 'then' is required, ensure 'first' is too */
+ pe_order_implies_then = 0x20, /* If 'first' is required, ensure 'then' is too */
+ pe_order_promoted_implies_first = 0x40, /* If 'then' is required and then's rsc is promoted, ensure 'first' becomes required too */
+
+ /* first requires then to be both runnable and migrate runnable. */
+ pe_order_implies_first_migratable = 0x80,
+
+ pe_order_runnable_left = 0x100, /* 'then' requires 'first' to be runnable */
+
+ pe_order_pseudo_left = 0x200, /* 'then' can only be pseudo if 'first' is runnable */
+ pe_order_implies_then_on_node = 0x400, /* If 'first' is required on 'nodeX',
+ * ensure instances of 'then' on 'nodeX' are too.
+ * Only really useful if 'then' is a clone and 'first' is not
+ */
+ pe_order_probe = 0x800, /* If 'first->rsc' is
+ * - running but about to stop, ignore the constraint
+ * - otherwise, behave as runnable_left
+ */
+
+ pe_order_restart = 0x1000, /* 'then' is runnable if 'first' is optional or runnable */
+ pe_order_stonith_stop = 0x2000, //<! \deprecated Will be removed in future release
+ pe_order_serialize_only = 0x4000, /* serialize */
+ pe_order_same_node = 0x8000, /* applies only if 'first' and 'then' are on same node */
+
+ pe_order_implies_first_printed = 0x10000, /* Like ..implies_first but only ensures 'first' is printed, not mandatory */
+ pe_order_implies_then_printed = 0x20000, /* Like ..implies_then but only ensures 'then' is printed, not mandatory */
+
+ pe_order_asymmetrical = 0x100000, /* Indicates asymmetrical one way ordering constraint. */
+ pe_order_load = 0x200000, /* Only relevant if... */
+ pe_order_one_or_more = 0x400000, /* 'then' is runnable only if one or more of its dependencies are too */
+ pe_order_anti_colocation = 0x800000,
+
+ pe_order_preserve = 0x1000000, /* Hack for breaking user ordering constraints with container resources */
+ pe_order_then_cancels_first = 0x2000000, // if 'then' becomes required, 'first' becomes optional
+ pe_order_trace = 0x4000000, /* test marker */
+
+#if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1)
+ // \deprecated Use pe_order_promoted_implies_first instead
+ pe_order_implies_first_master = pe_order_promoted_implies_first,
+#endif
+};
+/* *INDENT-ON* */
+
+typedef struct pe_action_wrapper_s {
+ enum pe_ordering type;
+ enum pe_link_state state;
+ pe_action_t *action;
+} pe_action_wrapper_t;
+
+#if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1)
+#include <crm/pengine/pe_types_compat.h>
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // PCMK__CRM_PENGINE_PE_TYPES__H
diff --git a/include/crm/pengine/pe_types_compat.h b/include/crm/pengine/pe_types_compat.h
new file mode 100644
index 0000000..6f174c4
--- /dev/null
+++ b/include/crm/pengine/pe_types_compat.h
@@ -0,0 +1,63 @@
+/*
+ * 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 PCMK__CRM_PENGINE_PE_TYPES_COMPAT__H
+# define PCMK__CRM_PENGINE_PE_TYPES_COMPAT__H
+
+#include <crm/pengine/pe_types.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * \file
+ * \brief Deprecated Pacemaker scheduler API
+ * \ingroup pengine
+ * \deprecated Do not include this header directly. The scheduler APIs in this
+ * header, and the header itself, will be removed in a future
+ * release.
+ */
+
+//!@{
+//! \deprecated Do not use (unused by Pacemaker)
+enum pe_graph_flags {
+ pe_graph_none = 0x00000,
+ pe_graph_updated_first = 0x00001,
+ pe_graph_updated_then = 0x00002,
+ pe_graph_disable = 0x00004,
+};
+//!@}
+
+//!< \deprecated Use pe_action_t instead
+typedef struct pe_action_s action_t;
+
+//!< \deprecated Use pe_action_wrapper_t instead
+typedef struct pe_action_wrapper_s action_wrapper_t;
+
+//!< \deprecated Use pe_node_t instead
+typedef struct pe_node_s node_t;
+
+//!< \deprecated Use enum pe_quorum_policy instead
+typedef enum pe_quorum_policy no_quorum_policy_t;
+
+//!< \deprecated use pe_resource_t instead
+typedef struct pe_resource_s resource_t;
+
+//!< \deprecated Use pe_tag_t instead
+typedef struct pe_tag_s tag_t;
+
+//!< \deprecated Use pe_ticket_t instead
+typedef struct pe_ticket_s ticket_t;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // PCMK__CRM_PENGINE_PE_TYPES_COMPAT__H
diff --git a/include/crm/pengine/remote_internal.h b/include/crm/pengine/remote_internal.h
new file mode 100644
index 0000000..46d58fc
--- /dev/null
+++ b/include/crm/pengine/remote_internal.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2013-2019 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 PE_REMOTE__H
+# define PE_REMOTE__H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <glib.h> // gboolean
+#include <libxml/tree.h> // xmlNode
+#include <crm/pengine/status.h>
+
+bool xml_contains_remote_node(xmlNode *xml);
+bool pe__is_remote_node(const pe_node_t *node);
+bool pe__is_guest_node(const pe_node_t *node);
+bool pe__is_guest_or_remote_node(const pe_node_t *node);
+bool pe__is_bundle_node(const pe_node_t *node);
+bool pe__resource_is_remote_conn(const pe_resource_t *rsc,
+ const pe_working_set_t *data_set);
+pe_resource_t *pe__resource_contains_guest_node(const pe_working_set_t *data_set,
+ const pe_resource_t *rsc);
+void pe_foreach_guest_node(const pe_working_set_t *data_set, const pe_node_t *host,
+ void (*helper)(const pe_node_t*, void*), void *user_data);
+xmlNode *pe_create_remote_xml(xmlNode *parent, const char *uname,
+ const char *container_id, const char *migrateable,
+ const char *is_managed, const char *start_timeout,
+ const char *server, const char *port);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/crm/pengine/rules.h b/include/crm/pengine/rules.h
new file mode 100644
index 0000000..264bd69
--- /dev/null
+++ b/include/crm/pengine/rules.h
@@ -0,0 +1,80 @@
+/*
+ * 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 PCMK__CRM_PENGINE_RULES__H
+# define PCMK__CRM_PENGINE_RULES__H
+
+# include <glib.h>
+# include <crm/crm.h>
+# include <crm/common/iso8601.h>
+# include <crm/pengine/common.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+enum expression_type {
+ not_expr = 0,
+ nested_rule = 1,
+ attr_expr = 2,
+ loc_expr = 3,
+ role_expr = 4,
+ time_expr = 5,
+#if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1)
+ //! \deprecated Do not use (will be removed in a future release)
+ version_expr = 6,
+#endif
+ rsc_expr = 7,
+ op_expr = 8,
+};
+
+enum expression_type find_expression_type(xmlNode * expr);
+
+gboolean pe_evaluate_rules(xmlNode *ruleset, GHashTable *node_hash,
+ crm_time_t *now, crm_time_t *next_change);
+
+gboolean pe_test_rule(xmlNode *rule, GHashTable *node_hash,
+ enum rsc_role_e role, crm_time_t *now,
+ crm_time_t *next_change, pe_match_data_t *match_data);
+
+gboolean pe_test_expression(xmlNode *expr, GHashTable *node_hash,
+ enum rsc_role_e role, crm_time_t *now,
+ crm_time_t *next_change,
+ pe_match_data_t *match_data);
+
+void pe_eval_nvpairs(xmlNode *top, const xmlNode *xml_obj, const char *set_name,
+ const pe_rule_eval_data_t *rule_data, GHashTable *hash,
+ const char *always_first, gboolean overwrite,
+ crm_time_t *next_change);
+
+void pe_unpack_nvpairs(xmlNode *top, const xmlNode *xml_obj,
+ const char *set_name, GHashTable *node_hash,
+ GHashTable *hash, const char *always_first,
+ gboolean overwrite, crm_time_t *now,
+ crm_time_t *next_change);
+
+char *pe_expand_re_matches(const char *string,
+ const pe_re_match_data_t *match_data);
+
+gboolean pe_eval_rules(xmlNode *ruleset, const pe_rule_eval_data_t *rule_data,
+ crm_time_t *next_change);
+gboolean pe_eval_expr(xmlNode *rule, const pe_rule_eval_data_t *rule_data,
+ crm_time_t *next_change);
+gboolean pe_eval_subexpr(xmlNode *expr, const pe_rule_eval_data_t *rule_data,
+ crm_time_t *next_change);
+
+#if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1)
+#include <crm/pengine/rules_compat.h>
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/crm/pengine/rules_compat.h b/include/crm/pengine/rules_compat.h
new file mode 100644
index 0000000..95fc9ac
--- /dev/null
+++ b/include/crm/pengine/rules_compat.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2004-2021 the Pacemaker project contributors
+ *
+ * The version control history for this file may have further details.
+ *
+ * This source code is licensed under the GNU Lesser General Public License
+ * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
+ */
+
+#ifndef PCMK__CRM_PENGINE_RULES_COMPAT__H
+# define PCMK__CRM_PENGINE_RULES_COMPAT__H
+
+#include <glib.h>
+#include <libxml/tree.h> // xmlNode
+#include <crm/common/iso8601.h> // crm_time_t
+#include <crm/pengine/pe_types.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * \file
+ * \brief Deprecated Pacemaker rule API
+ * \ingroup pengine
+ * \deprecated Do not include this header directly. The rule APIs in this
+ * header, and the header itself, will be removed in a future
+ * release.
+ */
+
+//! \deprecated Use pe_evaluate_rules() instead
+gboolean test_ruleset(xmlNode *ruleset, GHashTable *node_hash, crm_time_t *now);
+
+//! \deprecated Use pe_test_rule() instead
+gboolean test_rule(xmlNode *rule, GHashTable *node_hash, enum rsc_role_e role,
+ crm_time_t *now);
+
+//! \deprecated Use pe_test_rule() instead
+gboolean pe_test_rule_re(xmlNode *rule, GHashTable *node_hash,
+ enum rsc_role_e role, crm_time_t *now,
+ pe_re_match_data_t *re_match_data);
+
+//! \deprecated Use pe_test_rule() instead
+gboolean pe_test_rule_full(xmlNode *rule, GHashTable *node_hash,
+ enum rsc_role_e role, crm_time_t *now,
+ pe_match_data_t *match_data);
+
+//! \deprecated Use pe_test_expression() instead
+gboolean test_expression(xmlNode *expr, GHashTable *node_hash,
+ enum rsc_role_e role, crm_time_t *now);
+
+//! \deprecated Use pe_test_expression() instead
+gboolean pe_test_expression_re(xmlNode *expr, GHashTable *node_hash,
+ enum rsc_role_e role, crm_time_t *now,
+ pe_re_match_data_t *re_match_data);
+
+//! \deprecated Use pe_test_expression() instead
+gboolean pe_test_expression_full(xmlNode *expr, GHashTable *node_hash,
+ enum rsc_role_e role,
+ crm_time_t *now, pe_match_data_t *match_data);
+
+//! \deprecated Use pe_unpack_nvpairs() instead
+void unpack_instance_attributes(xmlNode *top, xmlNode *xml_obj,
+ const char *set_name, GHashTable *node_hash,
+ GHashTable *hash, const char *always_first,
+ gboolean overwrite, crm_time_t *now);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // PCMK__CRM_PENGINE_RULES_COMPAT__H
diff --git a/include/crm/pengine/rules_internal.h b/include/crm/pengine/rules_internal.h
new file mode 100644
index 0000000..9b81963
--- /dev/null
+++ b/include/crm/pengine/rules_internal.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2015-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 RULES_INTERNAL_H
+#define RULES_INTERNAL_H
+
+#include <glib.h>
+#include <libxml/tree.h>
+
+#include <crm/common/iso8601.h>
+#include <crm/pengine/common.h>
+#include <crm/pengine/rules.h>
+
+GList *pe_unpack_alerts(const xmlNode *alerts);
+void pe_free_alert_list(GList *alert_list);
+
+gboolean pe__eval_attr_expr(const xmlNode *expr,
+ const pe_rule_eval_data_t *rule_data);
+int pe__eval_date_expr(const xmlNode *expr,
+ const pe_rule_eval_data_t *rule_data,
+ crm_time_t *next_change);
+gboolean pe__eval_op_expr(const xmlNode *expr,
+ const pe_rule_eval_data_t *rule_data);
+gboolean pe__eval_role_expr(const xmlNode *expr,
+ const pe_rule_eval_data_t *rule_data);
+gboolean pe__eval_rsc_expr(const xmlNode *expr,
+ const pe_rule_eval_data_t *rule_data);
+
+int pe_cron_range_satisfied(const crm_time_t *now, const xmlNode *cron_spec);
+
+#endif
diff --git a/include/crm/pengine/status.h b/include/crm/pengine/status.h
new file mode 100644
index 0000000..145a166
--- /dev/null
+++ b/include/crm/pengine/status.h
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2004-2023 the Pacemaker project contributors
+ *
+ * The version control history for this file may have further details.
+ *
+ * This source code is licensed under the GNU Lesser General Public License
+ * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
+ */
+
+#ifndef PCMK__CRM_PENGINE_STATUS__H
+# define PCMK__CRM_PENGINE_STATUS__H
+
+# include <glib.h> // gboolean
+# include <stdbool.h> // bool
+# include <crm/common/util.h> // pcmk_is_set()
+# include <crm/common/iso8601.h>
+# include <crm/pengine/common.h>
+# include <crm/pengine/pe_types.h> // pe_node_t, pe_resource_t, etc.
+# include <crm/pengine/complex.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*!
+ * \file
+ * \brief Cluster status and scheduling
+ * \ingroup pengine
+ */
+
+const char *rsc_printable_id(const pe_resource_t *rsc);
+gboolean cluster_status(pe_working_set_t * data_set);
+pe_working_set_t *pe_new_working_set(void);
+void pe_free_working_set(pe_working_set_t *data_set);
+void set_working_set_defaults(pe_working_set_t * data_set);
+void cleanup_calculations(pe_working_set_t * data_set);
+void pe_reset_working_set(pe_working_set_t *data_set);
+pe_resource_t *pe_find_resource(GList *rsc_list, const char *id_rh);
+pe_resource_t *pe_find_resource_with_flags(GList *rsc_list, const char *id, enum pe_find flags);
+pe_node_t *pe_find_node(const GList *node_list, const char *node_name);
+pe_node_t *pe_find_node_id(const GList *node_list, const char *id);
+pe_node_t *pe_find_node_any(const GList *node_list, const char *id,
+ const char *node_name);
+GList *find_operations(const char *rsc, const char *node, gboolean active_filter,
+ pe_working_set_t * data_set);
+void calculate_active_ops(const GList *sorted_op_list, int *start_index,
+ int *stop_index);
+int pe_bundle_replicas(const pe_resource_t *rsc);
+
+/*!
+ * \brief Check whether a resource is any clone type
+ *
+ * \param[in] rsc Resource to check
+ *
+ * \return true if resource is clone, false otherwise
+ */
+static inline bool
+pe_rsc_is_clone(const pe_resource_t *rsc)
+{
+ return rsc && (rsc->variant == pe_clone);
+}
+
+/*!
+ * \brief Check whether a resource is a globally unique clone
+ *
+ * \param[in] rsc Resource to check
+ *
+ * \return true if resource is unique clone, false otherwise
+ */
+static inline bool
+pe_rsc_is_unique_clone(const pe_resource_t *rsc)
+{
+ return pe_rsc_is_clone(rsc) && pcmk_is_set(rsc->flags, pe_rsc_unique);
+}
+
+/*!
+ * \brief Check whether a resource is an anonymous clone
+ *
+ * \param[in] rsc Resource to check
+ *
+ * \return true if resource is anonymous clone, false otherwise
+ */
+static inline bool
+pe_rsc_is_anon_clone(const pe_resource_t *rsc)
+{
+ return pe_rsc_is_clone(rsc) && !pcmk_is_set(rsc->flags, pe_rsc_unique);
+}
+
+/*!
+ * \brief Check whether a resource is part of a bundle
+ *
+ * \param[in] rsc Resource to check
+ *
+ * \return true if resource is part of a bundle, false otherwise
+ */
+static inline bool
+pe_rsc_is_bundled(const pe_resource_t *rsc)
+{
+ if (rsc == NULL) {
+ return false;
+ }
+ while (rsc->parent != NULL) {
+ rsc = rsc->parent;
+ }
+ return rsc->variant == pe_container;
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/crm/services.h b/include/crm/services.h
new file mode 100644
index 0000000..4f0a5f0
--- /dev/null
+++ b/include/crm/services.h
@@ -0,0 +1,432 @@
+/*
+ * Copyright 2010-2023 the Pacemaker project contributors
+ *
+ * The version control history for this file may have further details.
+ *
+ * This source code is licensed under the GNU Lesser General Public License
+ * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
+ */
+
+#ifndef PCMK__CRM_SERVICES__H
+# define PCMK__CRM_SERVICES__H
+
+
+# include <glib.h>
+# include <stdio.h>
+# include <stdint.h>
+# include <string.h>
+# include <stdbool.h>
+# include <sys/types.h>
+
+# include <crm_config.h> // OCF_ROOT_DIR
+# include <crm/common/agents.h>
+# include <crm/common/results.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*!
+ * \file
+ * \brief Services API
+ * \ingroup core
+ */
+
+/* TODO: Autodetect these two ?*/
+# ifndef SYSTEMCTL
+# define SYSTEMCTL "/bin/systemctl"
+# endif
+
+/* This is the string passed in the OCF_EXIT_REASON_PREFIX environment variable.
+ * The stderr output that occurs after this prefix is encountered is considered
+ * the exit reason for a completed operation.
+ */
+#define PCMK_OCF_REASON_PREFIX "ocf-exit-reason:"
+
+// Agent version to use if agent doesn't specify one
+#define PCMK_DEFAULT_AGENT_VERSION "0.1"
+
+enum lsb_exitcode {
+ PCMK_LSB_OK = 0,
+ PCMK_LSB_UNKNOWN_ERROR = 1,
+ PCMK_LSB_INVALID_PARAM = 2,
+ PCMK_LSB_UNIMPLEMENT_FEATURE = 3,
+ PCMK_LSB_INSUFFICIENT_PRIV = 4,
+ PCMK_LSB_NOT_INSTALLED = 5,
+ PCMK_LSB_NOT_CONFIGURED = 6,
+ PCMK_LSB_NOT_RUNNING = 7,
+};
+
+// LSB uses different return codes for status actions
+enum lsb_status_exitcode {
+ PCMK_LSB_STATUS_OK = 0,
+ PCMK_LSB_STATUS_VAR_PID = 1,
+ PCMK_LSB_STATUS_VAR_LOCK = 2,
+ PCMK_LSB_STATUS_NOT_RUNNING = 3,
+ PCMK_LSB_STATUS_UNKNOWN = 4,
+
+ /* custom codes should be in the 150-199 range reserved for application use */
+ PCMK_LSB_STATUS_NOT_INSTALLED = 150,
+ PCMK_LSB_STATUS_INSUFFICIENT_PRIV = 151,
+};
+
+//!@{
+//! \deprecated Do not use
+
+enum nagios_exitcode {
+ NAGIOS_STATE_OK = 0,
+ NAGIOS_STATE_WARNING = 1,
+ NAGIOS_STATE_CRITICAL = 2,
+ NAGIOS_STATE_UNKNOWN = 3,
+
+ /* This is a custom Pacemaker value (not a nagios convention), used as an
+ * intermediate value between the services library and the executor, so the
+ * executor can map it to the corresponding OCF code.
+ */
+ NAGIOS_INSUFFICIENT_PRIV = 100,
+
+#if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1)
+ NAGIOS_STATE_DEPENDENT = 4,
+ NAGIOS_NOT_INSTALLED = 101,
+#endif
+};
+
+//!@}
+
+enum svc_action_flags {
+ /* On timeout, only kill pid, do not kill entire pid group */
+ SVC_ACTION_LEAVE_GROUP = 0x01,
+ SVC_ACTION_NON_BLOCKED = 0x02,
+};
+
+typedef struct svc_action_private_s svc_action_private_t;
+
+/*!
+ * \brief Object for executing external actions
+ *
+ * \note This object should never be instantiated directly, but instead created
+ * using one of the constructor functions (resources_action_create() for
+ * resource agents, services_alert_create() for alert agents, or
+ * services_action_create_generic() for generic executables). Similarly,
+ * do not use sizeof() on this struct.
+ */
+/*
+ * NOTE: Internally, services__create_resource_action() is preferable to
+ * resources_action_create().
+ */
+typedef struct svc_action_s {
+ /*! Operation key (<resource>_<action>_<interval>) for resource actions,
+ * XML ID for alert actions, or NULL for generic actions
+ */
+ char *id;
+
+ //! XML ID of resource being executed for resource actions, otherwise NULL
+ char *rsc;
+
+ //! Name of action being executed for resource actions, otherwise NULL
+ char *action;
+
+ //! Action interval for recurring resource actions, otherwise 0
+ guint interval_ms;
+
+ //! Resource standard for resource actions, otherwise NULL
+ char *standard;
+
+ //! Resource provider for resource actions that require it, otherwise NULL
+ char *provider;
+
+ //! Resource agent name for resource actions, otherwise NULL
+ char *agent;
+
+ int timeout; //!< Action timeout (in milliseconds)
+
+ /*! A hash table of name/value pairs to use as parameters for resource and
+ * alert actions, otherwise NULL. These will be used to set environment
+ * variables for non-fencing resource agents and alert agents, and to send
+ * stdin to fence agents.
+ */
+ GHashTable *params;
+
+ int rc; //!< Exit status of action (set by library upon completion)
+
+ //!@{
+ //! This field should be treated as internal to Pacemaker
+ int pid; // Process ID of child
+ int cancel; // Whether this is a cancellation of a recurring action
+ //!@}
+
+ int status; //!< Execution status (enum pcmk_exec_status set by library)
+
+ /*! Action counter (set by library for resource actions, or by caller
+ * otherwise)
+ */
+ int sequence;
+
+ //!@{
+ //! This field should be treated as internal to Pacemaker
+ int expected_rc; // Unused
+ int synchronous; // Whether execution should be synchronous (blocking)
+ //!@}
+
+ enum svc_action_flags flags; //!< Flag group of enum svc_action_flags
+ char *stderr_data; //!< Action stderr (set by library)
+ char *stdout_data; //!< Action stdout (set by library)
+ void *cb_data; //!< For caller's use (not used by library)
+
+ //! This field should be treated as internal to Pacemaker
+ svc_action_private_t *opaque;
+} svc_action_t;
+
+/*!
+ * \brief Get a list of files or directories in a given path
+ *
+ * \param[in] root Full path to a directory to read
+ * \param[in] files Return list of files if TRUE or directories if FALSE
+ * \param[in] executable If TRUE and files is TRUE, only return executable files
+ *
+ * \return List of what was found as char * items.
+ * \note The caller is responsibile for freeing the result with
+ * g_list_free_full(list, free).
+ */
+GList *get_directory_list(const char *root, gboolean files,
+ gboolean executable);
+
+/*!
+ * \brief Get a list of providers
+ *
+ * \param[in] standard List providers of this resource agent standard
+ *
+ * \return List of providers as char * list items (or NULL if standard does not
+ * support providers)
+ * \note The caller is responsible for freeing the result using
+ * g_list_free_full(list, free).
+ */
+GList *resources_list_providers(const char *standard);
+
+/*!
+ * \brief Get a list of resource agents
+ *
+ * \param[in] standard List agents of this standard (or NULL for all)
+ * \param[in] provider List agents of this provider (or NULL for all)
+ *
+ * \return List of resource agents as char * items.
+ * \note The caller is responsible for freeing the result using
+ * g_list_free_full(list, free).
+ */
+GList *resources_list_agents(const char *standard, const char *provider);
+
+/*!
+ * Get list of available standards
+ *
+ * \return List of resource standards as char * items.
+ * \note The caller is responsible for freeing the result using
+ * g_list_free_full(list, free).
+ */
+GList *resources_list_standards(void);
+
+/*!
+ * \brief Check whether a resource agent exists on the local host
+ *
+ * \param[in] standard Resource agent standard of agent to check
+ * \param[in] provider Provider of agent to check (or NULL)
+ * \param[in] agent Name of agent to check
+ *
+ * \return TRUE if agent exists locally, otherwise FALSE
+ */
+gboolean resources_agent_exists(const char *standard, const char *provider,
+ const char *agent);
+
+/*!
+ * \brief Create a new resource action
+ *
+ * \param[in] name Name of resource that action is for
+ * \param[in] standard Resource agent standard
+ * \param[in] provider Resource agent provider
+ * \param[in] agent Resource agent name
+ * \param[in] action Name of action to create
+ * \param[in] interval_ms How often to repeat action (if 0, execute once)
+ * \param[in] timeout Error if not complete within this time (ms)
+ * \param[in,out] params Action parameters
+ * \param[in] flags Group of enum svc_action_flags
+ *
+ * \return Newly allocated action
+ * \note This function assumes ownership of (and may free) \p params.
+ * \note The caller is responsible for freeing the return value using
+ * services_action_free().
+ */
+svc_action_t *resources_action_create(const char *name, const char *standard,
+ const char *provider, const char *agent,
+ const char *action, guint interval_ms,
+ int timeout, GHashTable *params,
+ enum svc_action_flags flags);
+
+/*!
+ * \brief Reschedule a recurring action for immediate execution
+ *
+ * \param[in] name Name of resource that action is for
+ * \param[in] action Action's name
+ * \param[in] interval_ms Action's interval (in milliseconds)
+ *
+ * \return TRUE on success, otherwise FALSE
+ */
+gboolean services_action_kick(const char *name, const char *action,
+ guint interval_ms);
+
+const char *resources_find_service_class(const char *agent);
+
+/*!
+ * \brief Request execution of an arbitrary command
+ *
+ * This API has useful infrastructure in place to be able to run a command
+ * in the background and get notified via a callback when the command finishes.
+ *
+ * \param[in] exec Full path to command executable
+ * \param[in] args NULL-terminated list of arguments to pass to command
+ *
+ * \return Newly allocated action object
+ */
+svc_action_t *services_action_create_generic(const char *exec,
+ const char *args[]);
+
+void services_action_cleanup(svc_action_t *op);
+void services_action_free(svc_action_t *op);
+int services_action_user(svc_action_t *op, const char *user);
+gboolean services_action_sync(svc_action_t *op);
+
+/*!
+ * \brief Run an action asynchronously, with callback after process is forked
+ *
+ * \param[in,out] op Action to run
+ * \param[in] action_callback Function to call when action completes
+ * (if NULL, any previously set callback will
+ * continue to be used)
+ * \param[in] action_fork_callback Function to call after child process is
+ * forked for action (if NULL, any
+ * previously set callback will continue to
+ * be used)
+ *
+ * \retval TRUE if the caller should not free or otherwise use \p op again,
+ * because one of these conditions is true:
+ *
+ * * \p op is NULL.
+ * * The action was successfully initiated, in which case
+ * \p action_fork_callback has been called, but \p action_callback has
+ * not (it will be called when the action completes).
+ * * The action's ID matched an existing recurring action. The existing
+ * action has taken over the callback and callback data from \p op
+ * and has been re-initiated asynchronously, and \p op has been freed.
+ * * Another action for the same resource is in flight, and \p op will
+ * be blocked until it completes.
+ * * The action could not be initiated, and is either non-recurring or
+ * being cancelled. \p action_fork_callback has not been called, but
+ * \p action_callback has, and \p op has been freed.
+ *
+ * \retval FALSE if \op is still valid, because the action cannot be initiated,
+ * and is a recurring action that is not being cancelled.
+ * \p action_fork_callback has not been called, but \p action_callback
+ * has, and a timer has been set for the next invocation of \p op.
+ */
+gboolean services_action_async_fork_notify(svc_action_t *op,
+ void (*action_callback) (svc_action_t *),
+ void (*action_fork_callback) (svc_action_t *));
+
+/*!
+ * \brief Request asynchronous execution of an action
+ *
+ * \param[in,out] op Action to execute
+ * \param[in] action_callback Function to call when the action completes
+ * (if NULL, any previously set callback will
+ * continue to be used)
+ *
+ * \retval TRUE if the caller should not free or otherwise use \p op again,
+ * because one of these conditions is true:
+ *
+ * * \p op is NULL.
+ * * The action was successfully initiated, in which case
+ * \p action_callback has not been called (it will be called when the
+ * action completes).
+ * * The action's ID matched an existing recurring action. The existing
+ * action has taken over the callback and callback data from \p op
+ * and has been re-initiated asynchronously, and \p op has been freed.
+ * * Another action for the same resource is in flight, and \p op will
+ * be blocked until it completes.
+ * * The action could not be initiated, and is either non-recurring or
+ * being cancelled. \p action_callback has been called, and \p op has
+ * been freed.
+ *
+ * \retval FALSE if \op is still valid, because the action cannot be initiated,
+ * and is a recurring action that is not being cancelled.
+ * \p action_callback has been called, and a timer has been set for the
+ * next invocation of \p op.
+ */
+gboolean services_action_async(svc_action_t *op,
+ void (*action_callback) (svc_action_t *));
+
+gboolean services_action_cancel(const char *name, const char *action,
+ guint interval_ms);
+
+/* functions for alert agents */
+svc_action_t *services_alert_create(const char *id, const char *exec,
+ int timeout, GHashTable *params,
+ int sequence, void *cb_data);
+gboolean services_alert_async(svc_action_t *action,
+ void (*cb)(svc_action_t *op));
+
+enum ocf_exitcode services_result2ocf(const char *standard, const char *action,
+ int exit_status);
+
+ static inline const char *services_ocf_exitcode_str(enum ocf_exitcode code) {
+ switch (code) {
+ case PCMK_OCF_OK:
+ return "ok";
+ case PCMK_OCF_UNKNOWN_ERROR:
+ return "error";
+ case PCMK_OCF_INVALID_PARAM:
+ return "invalid parameter";
+ case PCMK_OCF_UNIMPLEMENT_FEATURE:
+ return "unimplemented feature";
+ case PCMK_OCF_INSUFFICIENT_PRIV:
+ return "insufficient privileges";
+ case PCMK_OCF_NOT_INSTALLED:
+ return "not installed";
+ case PCMK_OCF_NOT_CONFIGURED:
+ return "not configured";
+ case PCMK_OCF_NOT_RUNNING:
+ return "not running";
+ case PCMK_OCF_RUNNING_PROMOTED:
+ return "promoted";
+ case PCMK_OCF_FAILED_PROMOTED:
+ return "promoted (failed)";
+ case PCMK_OCF_DEGRADED:
+ return "OCF_DEGRADED";
+ case PCMK_OCF_DEGRADED_PROMOTED:
+ return "promoted (degraded)";
+
+#if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1)
+ case PCMK_OCF_NOT_SUPPORTED:
+ return "not supported (DEPRECATED STATUS)";
+ case PCMK_OCF_CANCELLED:
+ return "cancelled (DEPRECATED STATUS)";
+ case PCMK_OCF_OTHER_ERROR:
+ return "other error (DEPRECATED STATUS)";
+ case PCMK_OCF_SIGNAL:
+ return "interrupted by signal (DEPRECATED STATUS)";
+ case PCMK_OCF_PENDING:
+ return "pending (DEPRECATED STATUS)";
+ case PCMK_OCF_TIMEOUT:
+ return "timeout (DEPRECATED STATUS)";
+#endif
+ default:
+ return "unknown";
+ }
+ }
+
+#if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1)
+#include <crm/services_compat.h>
+#endif
+
+# ifdef __cplusplus
+}
+# endif
+
+#endif /* __PCMK_SERVICES__ */
diff --git a/include/crm/services_compat.h b/include/crm/services_compat.h
new file mode 100644
index 0000000..97310f4
--- /dev/null
+++ b/include/crm/services_compat.h
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2010-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 PCMK__CRM_SERVICES_COMPAT__H
+# define PCMK__CRM_SERVICES_COMPAT__H
+
+
+#include <crm/common/results.h>
+#include <crm/services.h>
+#include <glib.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*!
+ * \file
+ * \brief Deprecated services API
+ * \ingroup core
+ * \deprecated Do not include this header directly. The service APIs in this
+ * header, and the header itself, will be removed in a future
+ * release.
+ */
+
+# ifndef LSB_ROOT_DIR
+ //! \deprecated Do not use
+# define LSB_ROOT_DIR "/etc/init.d"
+# endif
+
+//! \deprecated Use enum pcmk_exec_status instead
+enum op_status {
+ PCMK_LRM_OP_UNKNOWN = PCMK_EXEC_UNKNOWN,
+ PCMK_LRM_OP_PENDING = PCMK_EXEC_PENDING,
+ PCMK_LRM_OP_DONE = PCMK_EXEC_DONE,
+ PCMK_LRM_OP_CANCELLED = PCMK_EXEC_CANCELLED,
+ PCMK_LRM_OP_TIMEOUT = PCMK_EXEC_TIMEOUT,
+ PCMK_LRM_OP_NOTSUPPORTED = PCMK_EXEC_NOT_SUPPORTED,
+ PCMK_LRM_OP_ERROR = PCMK_EXEC_ERROR,
+ PCMK_LRM_OP_ERROR_HARD = PCMK_EXEC_ERROR_HARD,
+ PCMK_LRM_OP_ERROR_FATAL = PCMK_EXEC_ERROR_FATAL,
+ PCMK_LRM_OP_NOT_INSTALLED = PCMK_EXEC_NOT_INSTALLED,
+ PCMK_LRM_OP_NOT_CONNECTED = PCMK_EXEC_NOT_CONNECTED,
+ PCMK_LRM_OP_INVALID = PCMK_EXEC_INVALID,
+};
+
+//! \deprecated Use resources_action_create() instead
+svc_action_t *services_action_create(const char *name, const char *action,
+ guint interval_ms, int timeout);
+
+//! \deprecated Use resources_list_agents() instead
+GList *services_list(void);
+
+//! \deprecated Use pcmk_exec_status_str() instead
+static inline const char *
+services_lrm_status_str(enum op_status status)
+{
+ return pcmk_exec_status_str((enum pcmk_exec_status) status);
+}
+
+//! \deprecated Use services_result2ocf() instead
+static inline enum ocf_exitcode
+services_get_ocf_exitcode(const char *action, int lsb_exitcode)
+{
+ /* For non-status actions, LSB and OCF share error code meaning <= 7 */
+ if (action && strcmp(action, "status") && strcmp(action, "monitor")) {
+ if ((lsb_exitcode < 0) || (lsb_exitcode > PCMK_LSB_NOT_RUNNING)) {
+ return PCMK_OCF_UNKNOWN_ERROR;
+ }
+ return (enum ocf_exitcode)lsb_exitcode;
+ }
+
+ /* status has different return codes */
+ switch (lsb_exitcode) {
+ case PCMK_LSB_STATUS_OK:
+ return PCMK_OCF_OK;
+ case PCMK_LSB_STATUS_NOT_INSTALLED:
+ return PCMK_OCF_NOT_INSTALLED;
+ case PCMK_LSB_STATUS_INSUFFICIENT_PRIV:
+ return PCMK_OCF_INSUFFICIENT_PRIV;
+ case PCMK_LSB_STATUS_VAR_PID:
+ case PCMK_LSB_STATUS_VAR_LOCK:
+ case PCMK_LSB_STATUS_NOT_RUNNING:
+ return PCMK_OCF_NOT_RUNNING;
+ }
+ return PCMK_OCF_UNKNOWN_ERROR;
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/crm/services_internal.h b/include/crm/services_internal.h
new file mode 100644
index 0000000..ada97e1
--- /dev/null
+++ b/include/crm/services_internal.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2010-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 PCMK__SERVICES_INTERNAL__H
+# define PCMK__SERVICES_INTERNAL__H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*!
+ * \brief Create a new resource action
+ *
+ * \param[in] name Name of resource
+ * \param[in] standard Resource agent standard
+ * \param[in] provider Resource agent provider
+ * \param[in] agent Resource agent name
+ * \param[in] action Name of action
+ * \param[in] interval_ms How often to repeat action (if 0, execute once)
+ * \param[in] timeout Error if not complete within this time (ms)
+ * \param[in,out] params Action parameters
+ * \param[in] flags Group of enum svc_action_flags
+ *
+ * \return NULL if not enough memory, otherwise newly allocated action instance
+ * (if its rc member is not PCMK_OCF_UNKNOWN, the action is invalid)
+ *
+ * \note This function assumes ownership of (and may free) \p params.
+ * \note The caller is responsible for freeing the return value using
+ * services_action_free().
+ */
+svc_action_t *services__create_resource_action(const char *name,
+ const char *standard,
+ const char *provider,
+ const char *agent,
+ const char *action,
+ guint interval_ms,
+ int timeout, GHashTable *params,
+ enum svc_action_flags flags);
+
+const char *services__exit_reason(const svc_action_t *action);
+char *services__grab_stdout(svc_action_t *action);
+char *services__grab_stderr(svc_action_t *action);
+
+void services__set_result(svc_action_t *action, int agent_status,
+ enum pcmk_exec_status exec_status,
+ const char *exit_reason);
+
+void services__format_result(svc_action_t *action, int agent_status,
+ enum pcmk_exec_status exec_status,
+ const char *format, ...) G_GNUC_PRINTF(4, 5);
+
+# ifdef __cplusplus
+}
+# endif
+
+#endif /* PCMK__SERVICES_INTERNAL__H */
diff --git a/include/crm/stonith-ng.h b/include/crm/stonith-ng.h
new file mode 100644
index 0000000..fa87599
--- /dev/null
+++ b/include/crm/stonith-ng.h
@@ -0,0 +1,707 @@
+/*
+ * 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 PCMK__CRM_STONITH_NG__H
+# define PCMK__CRM_STONITH_NG__H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * \file
+ * \brief Fencing aka. STONITH
+ * \ingroup fencing
+ */
+
+/* IMPORTANT: DLM source code includes this file directly, without having access
+ * to other Pacemaker headers on its include path, so this file should *not*
+ * include any other Pacemaker headers. (DLM might be updated to avoid the
+ * issue, but we should still follow this guideline for a long time after.)
+ */
+
+# include <dlfcn.h>
+# include <errno.h>
+# include <stdbool.h> // bool
+# include <stdint.h> // uint32_t
+# include <time.h> // time_t
+
+# define T_STONITH_NOTIFY_DISCONNECT "st_notify_disconnect"
+# define T_STONITH_NOTIFY_FENCE "st_notify_fence"
+# define T_STONITH_NOTIFY_HISTORY "st_notify_history"
+# define T_STONITH_NOTIFY_HISTORY_SYNCED "st_notify_history_synced"
+
+/* *INDENT-OFF* */
+enum stonith_state {
+ stonith_connected_command,
+ stonith_connected_query,
+ stonith_disconnected,
+};
+
+enum stonith_call_options {
+ st_opt_none = 0x00000000,
+ st_opt_verbose = 0x00000001,
+ st_opt_allow_suicide = 0x00000002,
+
+ st_opt_manual_ack = 0x00000008,
+ st_opt_discard_reply = 0x00000010,
+/* st_opt_all_replies = 0x00000020, */
+ st_opt_topology = 0x00000040,
+ st_opt_scope_local = 0x00000100,
+ st_opt_cs_nodeid = 0x00000200,
+ st_opt_sync_call = 0x00001000,
+ /*! Allow the timeout period for a callback to be adjusted
+ * based on the time the server reports the operation will take. */
+ st_opt_timeout_updates = 0x00002000,
+ /*! Only report back if operation is a success in callback */
+ st_opt_report_only_success = 0x00004000,
+ /* used where ever apropriate - e.g. cleanup of history */
+ st_opt_cleanup = 0x000080000,
+ /* used where ever apropriate - e.g. send out a history query to all nodes */
+ st_opt_broadcast = 0x000100000,
+};
+
+/*! Order matters here, do not change values */
+enum op_state
+{
+ st_query,
+ st_exec,
+ st_done,
+ st_duplicate,
+ st_failed,
+};
+
+// Supported fence agent interface standards
+enum stonith_namespace {
+ st_namespace_invalid,
+ st_namespace_any,
+ st_namespace_internal, // Implemented internally by Pacemaker
+
+ /* Neither of these projects are active any longer, but the fence agent
+ * interfaces they created are still in use and supported by Pacemaker.
+ */
+ st_namespace_rhcs, // Red Hat Cluster Suite compatible
+ st_namespace_lha, // Linux-HA compatible
+};
+
+enum stonith_namespace stonith_text2namespace(const char *namespace_s);
+const char *stonith_namespace2text(enum stonith_namespace st_namespace);
+enum stonith_namespace stonith_get_namespace(const char *agent,
+ const char *namespace_s);
+
+typedef struct stonith_key_value_s {
+ char *key;
+ char *value;
+ struct stonith_key_value_s *next;
+} stonith_key_value_t;
+
+typedef struct stonith_history_s {
+ char *target;
+ char *action;
+ char *origin;
+ char *delegate;
+ char *client;
+ int state;
+ time_t completed;
+ struct stonith_history_s *next;
+ long completed_nsec;
+ char *exit_reason;
+} stonith_history_t;
+
+typedef struct stonith_s stonith_t;
+
+typedef struct stonith_event_s
+{
+ char *id;
+ char *type; //!< \deprecated Will be removed in future release
+ char *message; //!< \deprecated Will be removed in future release
+ char *operation;
+
+ int result;
+ char *origin;
+ char *target;
+ char *action;
+ char *executioner;
+
+ char *device;
+
+ /*! The name of the client that initiated the action. */
+ char *client_origin;
+
+ //! \internal This field should be treated as internal to Pacemaker
+ void *opaque;
+} stonith_event_t;
+
+typedef struct stonith_callback_data_s {
+ int rc;
+ int call_id;
+ void *userdata;
+
+ //! \internal This field should be treated as internal to Pacemaker
+ void *opaque;
+} stonith_callback_data_t;
+
+typedef struct stonith_api_operations_s
+{
+ /*!
+ * \brief Destroy a fencer connection
+ *
+ * \param[in,out] st Fencer connection to destroy
+ */
+ int (*free) (stonith_t *st);
+
+ /*!
+ * \brief Connect to the local fencer
+ *
+ * \param[in,out] st Fencer connection to connect
+ * \param[in] name Client name to use
+ * \param[out] stonith_fd If NULL, use a main loop, otherwise
+ * store IPC file descriptor here
+ *
+ * \return Legacy Pacemaker return code
+ */
+ int (*connect) (stonith_t *st, const char *name, int *stonith_fd);
+
+ /*!
+ * \brief Disconnect from the local stonith daemon.
+ *
+ * \param[in,out] st Fencer connection to disconnect
+ *
+ * \return Legacy Pacemaker return code
+ */
+ int (*disconnect)(stonith_t *st);
+
+ /*!
+ * \brief Unregister a fence device with the local fencer
+ *
+ * \param[in,out] st Fencer connection to disconnect
+ * \param[in] options Group of enum stonith_call_options
+ * \param[in] name ID of fence device to unregister
+ *
+ * \return pcmk_ok (if synchronous) or positive call ID (if asynchronous)
+ * on success, otherwise a negative legacy Pacemaker return code
+ */
+ int (*remove_device)(stonith_t *st, int options, const char *name);
+
+ /*!
+ * \brief Register a fence device with the local fencer
+ *
+ * \param[in,out] st Fencer connection to use
+ * \param[in] options Group of enum stonith_call_options
+ * \param[in] id ID of fence device to register
+ * \param[in] namespace Type of fence agent to search for ("redhat"
+ * or "stonith-ng" for RHCS-style, "internal" for
+ * Pacemaker-internal devices, "heartbeat" for
+ * LHA-style, or "any" or NULL for any)
+ * \param[in] agent Name of fence agent for device
+ * \param[in] params Fence agent parameters for device
+ *
+ * \return pcmk_ok (if synchronous) or positive call ID (if asynchronous)
+ * on success, otherwise a negative legacy Pacemaker return code
+ */
+ int (*register_device)(stonith_t *st, int options, const char *id,
+ const char *namespace, const char *agent,
+ const stonith_key_value_t *params);
+
+ /*!
+ * \brief Unregister a fencing level for specified node with local fencer
+ *
+ * \param[in,out] st Fencer connection to use
+ * \param[in] options Group of enum stonith_call_options
+ * \param[in] node Target node to unregister level for
+ * \param[in] level Topology level number to unregister
+ *
+ * \return pcmk_ok (if synchronous) or positive call ID (if asynchronous)
+ * on success, otherwise a negative legacy Pacemaker return code
+ */
+ int (*remove_level)(stonith_t *st, int options, const char *node,
+ int level);
+
+ /*!
+ * \brief Register a fencing level for specified node with local fencer
+ *
+ * \param[in,out] st Fencer connection to use
+ * \param[in] options Group of enum stonith_call_options
+ * \param[in] node Target node to register level for
+ * \param[in] level Topology level number to register
+ * \param[in] device_list Devices to register in level
+ *
+ * \return pcmk_ok (if synchronous) or positive call ID (if asynchronous)
+ * on success, otherwise a negative legacy Pacemaker return code
+ */
+ int (*register_level)(stonith_t *st, int options, const char *node,
+ int level, const stonith_key_value_t *device_list);
+
+ /*!
+ * \brief Retrieve a fence agent's metadata
+ *
+ * \param[in,out] stonith Fencer connection
+ * \param[in] call_options Group of enum stonith_call_options
+ * (currently ignored)
+ * \param[in] agent Fence agent to query
+ * \param[in] namespace Type of fence agent to search for ("redhat"
+ * or "stonith-ng" for RHCS-style, "internal"
+ * for Pacemaker-internal devices, "heartbeat"
+ * for LHA-style, or "any" or NULL for any)
+ * \param[out] output Where to store metadata
+ * \param[in] timeout_sec Error if not complete within this time
+ *
+ * \return Legacy Pacemaker return code
+ * \note The caller is responsible for freeing *output using free().
+ */
+ int (*metadata)(stonith_t *stonith, int call_options, const char *agent,
+ const char *namespace, char **output, int timeout_sec);
+
+ /*!
+ * \brief Retrieve a list of installed fence agents
+ *
+ * \param[in,out] stonith Fencer connection to use
+ * \param[in] call_options Group of enum stonith_call_options
+ * (currently ignored)
+ * \param[in] namespace Type of fence agents to list ("redhat"
+ * or "stonith-ng" for RHCS-style, "internal" for
+ * Pacemaker-internal devices, "heartbeat" for
+ * LHA-style, or "any" or NULL for all)
+ * \param[out] devices Where to store agent list
+ * \param[in] timeout Error if unable to complete within this
+ * (currently ignored)
+ *
+ * \return Number of items in list on success, or negative errno otherwise
+ * \note The caller is responsible for freeing the returned list with
+ * stonith_key_value_freeall().
+ */
+ int (*list_agents)(stonith_t *stonith, int call_options,
+ const char *namespace, stonith_key_value_t **devices,
+ int timeout);
+
+ /*!
+ * \brief Get the output of a fence device's list action
+ *
+ * \param[in,out] stonith Fencer connection to use
+ * \param[in] call_options Group of enum stonith_call_options
+ * \param[in] id Fence device ID to run list for
+ * \param[out] list_info Where to store list output
+ * \param[in] timeout Error if unable to complete within this
+ *
+ * \return pcmk_ok (if synchronous) or positive call ID (if asynchronous)
+ * on success, otherwise a negative legacy Pacemaker return code
+ */
+ int (*list)(stonith_t *stonith, int call_options, const char *id,
+ char **list_info, int timeout);
+
+ /*!
+ * \brief Check whether a fence device is reachable by monitor action
+ *
+ * \param[in,out] stonith Fencer connection to use
+ * \param[in] call_options Group of enum stonith_call_options
+ * \param[in] id Fence device ID to run monitor for
+ * \param[in] timeout Error if unable to complete within this
+ *
+ * \return pcmk_ok (if synchronous) or positive call ID (if asynchronous)
+ * on success, otherwise a negative legacy Pacemaker return code
+ */
+ int (*monitor)(stonith_t *stonith, int call_options, const char *id,
+ int timeout);
+
+ /*!
+ * \brief Check whether a fence device target is reachable by status action
+ *
+ * \param[in,out] stonith Fencer connection to use
+ * \param[in] call_options Group of enum stonith_call_options
+ * \param[in] id Fence device ID to run status for
+ * \param[in] port Fence target to run status for
+ * \param[in] timeout Error if unable to complete within this
+ *
+ * \return pcmk_ok (if synchronous) or positive call ID (if asynchronous)
+ * on success, otherwise a negative legacy Pacemaker return code
+ */
+ int (*status)(stonith_t *stonith, int call_options, const char *id,
+ const char *port, int timeout);
+
+ /*!
+ * \brief List registered fence devices
+ *
+ * \param[in,out] stonith Fencer connection to use
+ * \param[in] call_options Group of enum stonith_call_options
+ * \param[in] target Fence target to run status for
+ * \param[out] devices Where to store list of fence devices
+ * \param[in] timeout Error if unable to complete within this
+ *
+ * \note If node is provided, only devices that can fence the node id
+ * will be returned.
+ *
+ * \return Number of items in list on success, or negative errno otherwise
+ */
+ int (*query)(stonith_t *stonith, int call_options, const char *target,
+ stonith_key_value_t **devices, int timeout);
+
+ /*!
+ * \brief Request that a target get fenced
+ *
+ * \param[in,out] stonith Fencer connection to use
+ * \param[in] call_options Group of enum stonith_call_options
+ * \param[in] node Fence target
+ * \param[in] action "on", "off", or "reboot"
+ * \param[in] timeout Default per-device timeout to use with
+ * each executed device
+ * \param[in] tolerance Accept result of identical fence action
+ * completed within this time
+ *
+ * \return pcmk_ok (if synchronous) or positive call ID (if asynchronous)
+ * on success, otherwise a negative legacy Pacemaker return code
+ */
+ int (*fence)(stonith_t *stonith, int call_options, const char *node,
+ const char *action, int timeout, int tolerance);
+
+ /*!
+ * \brief Manually confirm that a node has been fenced
+ *
+ * \param[in,out] stonith Fencer connection to use
+ * \param[in] call_options Group of enum stonith_call_options
+ * \param[in] target Fence target
+ *
+ * \return pcmk_ok (if synchronous) or positive call ID (if asynchronous)
+ * on success, otherwise a negative legacy Pacemaker return code
+ */
+ int (*confirm)(stonith_t *stonith, int call_options, const char *target);
+
+ /*!
+ * \brief List fencing actions that have occurred for a target
+ *
+ * \param[in,out] stonith Fencer connection to use
+ * \param[in] call_options Group of enum stonith_call_options
+ * \param[in] node Fence target
+ * \param[out] history Where to store list of fencing actions
+ * \param[in] timeout Error if unable to complete within this
+ *
+ * \return Legacy Pacemaker return code
+ */
+ int (*history)(stonith_t *stonith, int call_options, const char *node,
+ stonith_history_t **history, int timeout);
+
+ /*!
+ * \brief Register a callback for fence notifications
+ *
+ * \param[in,out] stonith Fencer connection to use
+ * \param[in] event Event to register for
+ * \param[in] callback Callback to register
+ *
+ * \return Legacy Pacemaker return code
+ */
+ int (*register_notification)(stonith_t *stonith, const char *event,
+ void (*callback)(stonith_t *st,
+ stonith_event_t *e));
+
+ /*!
+ * \brief Unregister callbacks for fence notifications
+ *
+ * \param[in,out] stonith Fencer connection to use
+ * \param[in] event Event to unregister callbacks for (NULL for all)
+ *
+ * \return Legacy Pacemaker return code
+ */
+ int (*remove_notification)(stonith_t *stonith, const char *event);
+
+ /*!
+ * \brief Register a callback for an asynchronous fencing result
+ *
+ * \param[in,out] stonith Fencer connection to use
+ * \param[in] call_id Call ID to register callback for
+ * \param[in] timeout Error if result not received in this time
+ * \param[in] options Group of enum stonith_call_options
+ * (respects \c st_opt_timeout_updates and
+ * \c st_opt_report_only_success)
+ * \param[in,out] user_data Pointer to pass to callback
+ * \param[in] callback_name Unique identifier for callback
+ * \param[in] callback Callback to register (may be called
+ * immediately if \p call_id indicates error)
+ *
+ * \return \c TRUE on success, \c FALSE if call_id indicates error,
+ * or -EINVAL if \p stonith is not valid
+ */
+ int (*register_callback)(stonith_t *stonith, int call_id, int timeout,
+ int options, void *user_data,
+ const char *callback_name,
+ void (*callback)(stonith_t *st,
+ stonith_callback_data_t *data));
+
+ /*!
+ * \brief Unregister callbacks for asynchronous fencing results
+ *
+ * \param[in,out] stonith Fencer connection to use
+ * \param[in] call_id If \p all_callbacks is false, call ID
+ * to unregister callback for
+ * \param[in] all_callbacks If true, unregister all callbacks
+ *
+ * \return pcmk_ok
+ */
+ int (*remove_callback)(stonith_t *stonith, int call_id, bool all_callbacks);
+
+ /*!
+ * \brief Unregister fencing level for specified node, pattern or attribute
+ *
+ * \param[in,out] st Fencer connection to use
+ * \param[in] options Group of enum stonith_call_options
+ * \param[in] node If not NULL, unregister level targeting this node
+ * \param[in] pattern If not NULL, unregister level targeting nodes
+ * whose names match this regular expression
+ * \param[in] attr If this and \p value are not NULL, unregister
+ * level targeting nodes with this node attribute
+ * set to \p value
+ * \param[in] value If this and \p attr are not NULL, unregister
+ * level targeting nodes with node attribute \p attr
+ * set to this
+ * \param[in] level Topology level number to remove
+ *
+ * \return pcmk_ok (if synchronous) or positive call ID (if asynchronous)
+ * on success, otherwise a negative legacy Pacemaker return code
+ * \note The caller should set only one of \p node, \p pattern, or \p attr
+ * and \p value.
+ */
+ int (*remove_level_full)(stonith_t *st, int options,
+ const char *node, const char *pattern,
+ const char *attr, const char *value, int level);
+
+ /*!
+ * \brief Register fencing level for specified node, pattern or attribute
+ *
+ * \param[in,out] st Fencer connection to use
+ * \param[in] options Group of enum stonith_call_options
+ * \param[in] node If not NULL, register level targeting this
+ * node by name
+ * \param[in] pattern If not NULL, register level targeting nodes
+ * whose names match this regular expression
+ * \param[in] attr If this and \p value are not NULL, register
+ * level targeting nodes with this node
+ * attribute set to \p value
+ * \param[in] value If this and \p attr are not NULL, register
+ * level targeting nodes with node attribute
+ * \p attr set to this
+ * \param[in] level Topology level number to remove
+ * \param[in] device_list Devices to use in level
+ *
+ * \return pcmk_ok (if synchronous) or positive call ID (if asynchronous)
+ * on success, otherwise a negative legacy Pacemaker return code
+ *
+ * \note The caller should set only one of node, pattern or attr/value.
+ */
+ int (*register_level_full)(stonith_t *st, int options,
+ const char *node, const char *pattern,
+ const char *attr, const char *value, int level,
+ const stonith_key_value_t *device_list);
+
+ /*!
+ * \brief Validate an arbitrary stonith device configuration
+ *
+ * \param[in,out] st Fencer connection to use
+ * \param[in] call_options Group of enum stonith_call_options
+ * \param[in] rsc_id ID used to replace CIB secrets in \p params
+ * \param[in] namespace_s Type of fence agent to validate ("redhat"
+ * or "stonith-ng" for RHCS-style, "internal"
+ * for Pacemaker-internal devices, "heartbeat"
+ * for LHA-style, or "any" or NULL for any)
+ * \param[in] agent Fence agent to validate
+ * \param[in] params Configuration parameters to pass to agent
+ * \param[in] timeout Fail if no response within this many seconds
+ * \param[out] output If non-NULL, where to store any agent output
+ * \param[out] error_output If non-NULL, where to store agent error output
+ *
+ * \return pcmk_ok if validation succeeds, -errno otherwise
+ * \note If pcmk_ok is returned, the caller is responsible for freeing
+ * the output (if requested) with free().
+ */
+ int (*validate)(stonith_t *st, int call_options, const char *rsc_id,
+ const char *namespace_s, const char *agent,
+ const stonith_key_value_t *params, int timeout,
+ char **output, char **error_output);
+
+ /*!
+ * \brief Request delayed fencing of a target
+ *
+ * \param[in,out] stonith Fencer connection to use
+ * \param[in] call_options Group of enum stonith_call_options
+ * \param[in] node Fence target
+ * \param[in] action "on", "off", or "reboot"
+ * \param[in] timeout Default per-device timeout to use with
+ * each executed device
+ * \param[in] tolerance Accept result of identical fence action
+ * completed within this time
+ * \param[in] delay Execute fencing after this delay (-1
+ * disables any delay from pcmk_delay_base
+ * and pcmk_delay_max)
+ *
+ * \return pcmk_ok (if synchronous) or positive call ID (if asynchronous)
+ * on success, otherwise a negative legacy Pacemaker return code
+ */
+ int (*fence_with_delay)(stonith_t *stonith, int call_options,
+ const char *node, const char *action, int timeout,
+ int tolerance, int delay);
+
+} stonith_api_operations_t;
+
+struct stonith_s
+{
+ enum stonith_state state;
+
+ int call_id;
+ int call_timeout; //!< \deprecated Unused
+ void *st_private;
+
+ stonith_api_operations_t *cmds;
+};
+/* *INDENT-ON* */
+
+/* Core functions */
+stonith_t *stonith_api_new(void);
+void stonith_api_delete(stonith_t * st);
+
+void stonith_dump_pending_callbacks(stonith_t * st);
+
+bool stonith_dispatch(stonith_t * st);
+
+stonith_key_value_t *stonith_key_value_add(stonith_key_value_t * kvp, const char *key,
+ const char *value);
+void stonith_key_value_freeall(stonith_key_value_t * kvp, int keys, int values);
+
+void stonith_history_free(stonith_history_t *history);
+
+// Convenience functions
+int stonith_api_connect_retry(stonith_t *st, const char *name,
+ int max_attempts);
+const char *stonith_op_state_str(enum op_state state);
+
+/* Basic helpers that allows nodes to be fenced and the history to be
+ * queried without mainloop or the caller understanding the full API
+ *
+ * At least one of nodeid and uname are required
+ */
+int stonith_api_kick(uint32_t nodeid, const char *uname, int timeout, bool off);
+time_t stonith_api_time(uint32_t nodeid, const char *uname, bool in_progress);
+
+/*
+ * Helpers for using the above functions without install-time dependencies
+ *
+ * Usage:
+ * #include <crm/stonith-ng.h>
+ *
+ * To turn a node off by corosync nodeid:
+ * stonith_api_kick_helper(nodeid, 120, 1);
+ *
+ * To check the last fence date/time (also by nodeid):
+ * last = stonith_api_time_helper(nodeid, 0);
+ *
+ * To check if fencing is in progress:
+ * if(stonith_api_time_helper(nodeid, 1) > 0) { ... }
+ *
+ * eg.
+
+ #include <stdio.h>
+ #include <time.h>
+ #include <crm/stonith-ng.h>
+ int
+ main(int argc, char ** argv)
+ {
+ int rc = 0;
+ int nodeid = 102;
+
+ rc = stonith_api_time_helper(nodeid, 0);
+ printf("%d last fenced at %s\n", nodeid, ctime(rc));
+
+ rc = stonith_api_kick_helper(nodeid, 120, 1);
+ printf("%d fence result: %d\n", nodeid, rc);
+
+ rc = stonith_api_time_helper(nodeid, 0);
+ printf("%d last fenced at %s\n", nodeid, ctime(rc));
+
+ return 0;
+ }
+
+ */
+
+# define STONITH_LIBRARY "libstonithd.so.26"
+
+typedef int (*st_api_kick_fn) (int nodeid, const char *uname, int timeout, bool off);
+typedef time_t (*st_api_time_fn) (int nodeid, const char *uname, bool in_progress);
+
+static inline int
+stonith_api_kick_helper(uint32_t nodeid, int timeout, bool off)
+{
+ static void *st_library = NULL;
+ static st_api_kick_fn st_kick_fn;
+
+ if (st_library == NULL) {
+ st_library = dlopen(STONITH_LIBRARY, RTLD_LAZY);
+ }
+ if (st_library && st_kick_fn == NULL) {
+ st_kick_fn = (st_api_kick_fn) dlsym(st_library, "stonith_api_kick");
+ }
+ if (st_kick_fn == NULL) {
+#ifdef ELIBACC
+ return -ELIBACC;
+#else
+ return -ENOSYS;
+#endif
+ }
+
+ return (*st_kick_fn) (nodeid, NULL, timeout, off);
+}
+
+static inline time_t
+stonith_api_time_helper(uint32_t nodeid, bool in_progress)
+{
+ static void *st_library = NULL;
+ static st_api_time_fn st_time_fn;
+
+ if (st_library == NULL) {
+ st_library = dlopen(STONITH_LIBRARY, RTLD_LAZY);
+ }
+ if (st_library && st_time_fn == NULL) {
+ st_time_fn = (st_api_time_fn) dlsym(st_library, "stonith_api_time");
+ }
+ if (st_time_fn == NULL) {
+ return 0;
+ }
+
+ return (*st_time_fn) (nodeid, NULL, in_progress);
+}
+
+/**
+ * Does the given agent describe a stonith resource that can exist?
+ *
+ * \param[in] agent What is the name of the agent?
+ * \param[in] timeout Timeout to use when querying. If 0 is given,
+ * use a default of 120.
+ *
+ * \return A boolean
+ */
+bool stonith_agent_exists(const char *agent, int timeout);
+
+/*!
+ * \brief Turn fence action into a more readable string
+ *
+ * \param[in] action Fence action
+ */
+const char *stonith_action_str(const char *action);
+
+#if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1)
+/* Normally we'd put this section in a separate file (crm/fencing/compat.h), but
+ * we can't do that for the reason noted at the top of this file. That does mean
+ * we have to duplicate these declarations where they're implemented.
+ */
+
+//! \deprecated Use stonith_get_namespace() instead
+const char *get_stonith_provider(const char *agent, const char *provider);
+
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif