diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 06:53:20 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 06:53:20 +0000 |
commit | e5a812082ae033afb1eed82c0f2df3d0f6bdc93f (patch) | |
tree | a6716c9275b4b413f6c9194798b34b91affb3cc7 /include/crm/common | |
parent | Initial commit. (diff) | |
download | pacemaker-e5a812082ae033afb1eed82c0f2df3d0f6bdc93f.tar.xz pacemaker-e5a812082ae033afb1eed82c0f2df3d0f6bdc93f.zip |
Adding upstream version 2.1.6.upstream/2.1.6
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'include/crm/common')
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 |