summaryrefslogtreecommitdiffstats
path: root/include/crm/common
diff options
context:
space:
mode:
Diffstat (limited to 'include/crm/common')
-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
42 files changed, 6422 insertions, 0 deletions
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