diff --git a/include/crm/cib/cib_types.h b/include/crm/cib/cib_types.h index 95c4ee3994..aed83967ea 100644 --- a/include/crm/cib/cib_types.h +++ b/include/crm/cib/cib_types.h @@ -1,186 +1,186 @@ /* * Copyright 2004-2022 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #ifndef PCMK__CRM_CIB_CIB_TYPES__H # define PCMK__CRM_CIB_CIB_TYPES__H # include // gboolean, GList # include // xmlNode # include # include #ifdef __cplusplus extern "C" { #endif /** * \file * \brief Data types for Cluster Information Base access * \ingroup cib */ enum cib_variant { cib_undefined, cib_native, cib_file, cib_remote, cib_database, }; enum cib_state { cib_connected_command, cib_connected_query, cib_disconnected }; enum cib_conn_type { cib_command, cib_query, cib_no_connection, cib_command_nonblocking, }; enum cib_call_options { cib_none = 0x00000000, cib_verbose = 0x00000001, //!< Prefer stderr to logs cib_xpath = 0x00000002, cib_multiple = 0x00000004, cib_can_create = 0x00000008, cib_discard_reply = 0x00000010, cib_no_children = 0x00000020, cib_xpath_address = 0x00000040, cib_mixed_update = 0x00000080, cib_scope_local = 0x00000100, cib_dryrun = 0x00000200, cib_sync_call = 0x00001000, cib_no_mtime = 0x00002000, cib_zero_copy = 0x00004000, cib_inhibit_notify = 0x00010000, cib_quorum_override = 0x00100000, cib_inhibit_bcast = 0x01000000, //!< \deprecated Will be removed in future cib_force_diff = 0x10000000 }; typedef struct cib_s cib_t; typedef struct cib_api_operations_s { int (*signon) (cib_t *cib, const char *name, enum cib_conn_type type); int (*signon_raw) (cib_t *cib, const char *name, enum cib_conn_type type, int *event_fd); int (*signoff) (cib_t *cib); int (*free) (cib_t *cib); int (*set_op_callback) (cib_t *cib, void (*callback) (const xmlNode *msg, int callid, int rc, xmlNode *output)); int (*add_notify_callback) (cib_t *cib, const char *event, void (*callback) (const char *event, xmlNode *msg)); int (*del_notify_callback) (cib_t *cib, const char *event, void (*callback) (const char *event, xmlNode *msg)); int (*set_connection_dnotify) (cib_t *cib, void (*dnotify) (gpointer user_data)); int (*inputfd) (cib_t *cib); int (*noop) (cib_t *cib, int call_options); int (*ping) (cib_t *cib, xmlNode **output_data, int call_options); int (*query) (cib_t *cib, const char *section, xmlNode **output_data, int call_options); int (*query_from) (cib_t *cib, const char *host, const char *section, xmlNode **output_data, int call_options); //! \deprecated This method will be removed and should not be used int (*is_master) (cib_t *cib); //! \deprecated Use the set_primary() method instead int (*set_master) (cib_t *cib, int call_options); //! \deprecated Use the set_secondary() method instead int (*set_slave) (cib_t *cib, int call_options); //! \deprecated This method will be removed and should not be used int (*set_slave_all) (cib_t *cib, int call_options); int (*sync) (cib_t *cib, const char *section, int call_options); int (*sync_from) (cib_t *cib, const char *host, const char *section, int call_options); int (*upgrade) (cib_t *cib, int call_options); int (*bump_epoch) (cib_t *cib, int call_options); int (*create) (cib_t *cib, const char *section, xmlNode *data, int call_options); int (*modify) (cib_t *cib, const char *section, xmlNode *data, int call_options); int (*update) (cib_t *cib, const char *section, xmlNode *data, int call_options); int (*replace) (cib_t *cib, const char *section, xmlNode *data, int call_options); int (*remove) (cib_t *cib, const char *section, xmlNode *data, int call_options); int (*erase) (cib_t *cib, xmlNode **output_data, int call_options); //! \deprecated This method does nothing and should not be called int (*delete_absolute) (cib_t *cib, const char *section, xmlNode *data, int call_options); int (*quit) (cib_t *cib, int call_options); int (*register_notification) (cib_t *cib, const char *callback, int enabled); gboolean (*register_callback) (cib_t *cib, int call_id, int timeout, gboolean only_success, void *user_data, const char *callback_name, void (*callback) (xmlNode*, int, int, xmlNode*, void *)); gboolean (*register_callback_full)(cib_t *cib, int call_id, int timeout, gboolean only_success, void *user_data, const char *callback_name, void (*callback)(xmlNode *, int, int, xmlNode *, void *), void (*free_func)(void *)); /*! * \brief Set the local CIB manager as the cluster's primary instance * - * \param[in] cib CIB connection - * \param[in] call_options Group of enum cib_call_options flags + * \param[in,out] cib CIB connection + * \param[in] call_options Group of enum cib_call_options flags * * \return Legacy Pacemaker return code (in particular, pcmk_ok on success) */ int (*set_primary)(cib_t *cib, int call_options); /*! * \brief Set the local CIB manager as a secondary instance * - * \param[in] cib CIB connection - * \param[in] call_options Group of enum cib_call_options flags + * \param[in,out] cib CIB connection + * \param[in] call_options Group of enum cib_call_options flags * * \return Legacy Pacemaker return code (in particular, pcmk_ok on success) */ int (*set_secondary)(cib_t *cib, int call_options); } cib_api_operations_t; struct cib_s { enum cib_state state; enum cib_conn_type type; enum cib_variant variant; int call_id; int call_timeout; void *variant_opaque; void *delegate_fn; GList *notify_list; void (*op_callback) (const xmlNode *msg, int call_id, int rc, xmlNode *output); cib_api_operations_t *cmds; }; #ifdef __cplusplus } #endif #endif // PCMK__CRM_CIB_CIB_TYPES__H diff --git a/include/crm/cib/internal.h b/include/crm/cib/internal.h index 70cbebd123..4c0de495e6 100644 --- a/include/crm/cib/internal.h +++ b/include/crm/cib/internal.h @@ -1,271 +1,271 @@ /* * Copyright 2004-2022 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #ifndef CIB_INTERNAL__H # define CIB_INTERNAL__H # include # include # include // Request types for CIB manager IPC/CPG #define PCMK__CIB_REQUEST_SECONDARY "cib_slave" #define PCMK__CIB_REQUEST_ALL_SECONDARY "cib_slave_all" #define PCMK__CIB_REQUEST_PRIMARY "cib_master" #define PCMK__CIB_REQUEST_SYNC_TO_ALL "cib_sync" #define PCMK__CIB_REQUEST_SYNC_TO_ONE "cib_sync_one" #define PCMK__CIB_REQUEST_IS_PRIMARY "cib_ismaster" #define PCMK__CIB_REQUEST_BUMP "cib_bump" #define PCMK__CIB_REQUEST_QUERY "cib_query" #define PCMK__CIB_REQUEST_CREATE "cib_create" #define PCMK__CIB_REQUEST_MODIFY "cib_modify" #define PCMK__CIB_REQUEST_DELETE "cib_delete" #define PCMK__CIB_REQUEST_ERASE "cib_erase" #define PCMK__CIB_REQUEST_REPLACE "cib_replace" #define PCMK__CIB_REQUEST_APPLY_PATCH "cib_apply_diff" #define PCMK__CIB_REQUEST_UPGRADE "cib_upgrade" #define PCMK__CIB_REQUEST_ABS_DELETE "cib_delete_alt" #define PCMK__CIB_REQUEST_NOOP "noop" #define PCMK__CIB_REQUEST_SHUTDOWN "cib_shutdown_req" # define F_CIB_CLIENTID "cib_clientid" # define F_CIB_CALLOPTS "cib_callopt" # define F_CIB_CALLID "cib_callid" # define F_CIB_CALLDATA "cib_calldata" # define F_CIB_OPERATION "cib_op" # define F_CIB_ISREPLY "cib_isreplyto" # define F_CIB_SECTION "cib_section" # define F_CIB_HOST "cib_host" # define F_CIB_RC "cib_rc" # define F_CIB_UPGRADE_RC "cib_upgrade_rc" # define F_CIB_DELEGATED "cib_delegated_from" # define F_CIB_OBJID "cib_object" # define F_CIB_OBJTYPE "cib_object_type" # define F_CIB_EXISTING "cib_existing_object" # define F_CIB_SEENCOUNT "cib_seen" # define F_CIB_TIMEOUT "cib_timeout" # define F_CIB_UPDATE "cib_update" # define F_CIB_CALLBACK_TOKEN "cib_async_id" # define F_CIB_GLOBAL_UPDATE "cib_update" # define F_CIB_UPDATE_RESULT "cib_update_result" # define F_CIB_CLIENTNAME "cib_clientname" # define F_CIB_NOTIFY_TYPE "cib_notify_type" # define F_CIB_NOTIFY_ACTIVATE "cib_notify_activate" # define F_CIB_UPDATE_DIFF "cib_update_diff" # define F_CIB_USER "cib_user" # define F_CIB_LOCAL_NOTIFY_ID "cib_local_notify_id" # define F_CIB_PING_ID "cib_ping_id" # define F_CIB_SCHEMA_MAX "cib_schema_max" # define F_CIB_CHANGE_SECTION "cib_change_section" # define T_CIB "cib" # define T_CIB_NOTIFY "cib_notify" /* notify sub-types */ # define T_CIB_PRE_NOTIFY "cib_pre_notify" # define T_CIB_POST_NOTIFY "cib_post_notify" # define T_CIB_UPDATE_CONFIRM "cib_update_confirmation" # define T_CIB_REPLACE_NOTIFY "cib_refresh_notify" /*! * \internal * \enum cib_change_section_info * \brief Flags to indicate which sections of the CIB have changed */ enum cib_change_section_info { cib_change_section_none = 0, //!< No sections have changed cib_change_section_nodes = (1 << 0), //!< The nodes section has changed cib_change_section_alerts = (1 << 1), //!< The alerts section has changed cib_change_section_status = (1 << 2), //!< The status section has changed }; gboolean cib_diff_version_details(xmlNode * diff, int *admin_epoch, int *epoch, int *updates, int *_admin_epoch, int *_epoch, int *_updates); gboolean cib_read_config(GHashTable * options, xmlNode * current_cib); void verify_cib_options(GHashTable * options); gboolean cib_internal_config_changed(xmlNode * diff); typedef struct cib_notify_client_s { const char *event; const char *obj_id; /* implement one day */ const char *obj_type; /* implement one day */ void (*callback) (const char *event, xmlNode * msg); } cib_notify_client_t; typedef struct cib_callback_client_s { void (*callback) (xmlNode *, int, int, xmlNode *, void *); const char *id; void *user_data; gboolean only_success; struct timer_rec_s *timer; void (*free_func)(void *); } cib_callback_client_t; struct timer_rec_s { int call_id; int timeout; guint ref; cib_t *cib; }; #define cib__set_call_options(cib_call_opts, call_for, flags_to_set) do { \ cib_call_opts = pcmk__set_flags_as(__func__, __LINE__, \ LOG_TRACE, "CIB call", (call_for), (cib_call_opts), \ (flags_to_set), #flags_to_set); \ } while (0) #define cib__clear_call_options(cib_call_opts, call_for, flags_to_clear) do { \ cib_call_opts = pcmk__clear_flags_as(__func__, __LINE__, \ LOG_TRACE, "CIB call", (call_for), (cib_call_opts), \ (flags_to_clear), #flags_to_clear); \ } while (0) typedef int (*cib_op_t) (const char *, int, const char *, xmlNode *, xmlNode *, xmlNode *, xmlNode **, xmlNode **); cib_t *cib_new_variant(void); int cib_perform_op(const char *op, int call_options, cib_op_t * fn, gboolean is_query, const char *section, xmlNode * req, xmlNode * input, gboolean manage_counters, gboolean * config_changed, xmlNode * current_cib, xmlNode ** result_cib, xmlNode ** diff, xmlNode ** output); xmlNode *cib_create_op(int call_id, const char *token, const char *op, const char *host, const char *section, xmlNode * data, int call_options, const char *user_name); void cib_native_callback(cib_t * cib, xmlNode * msg, int call_id, int rc); void cib_native_notify(gpointer data, gpointer user_data); int cib_native_register_notification(cib_t * cib, const char *callback, int enabled); gboolean cib_client_register_callback(cib_t * cib, int call_id, int timeout, gboolean only_success, void *user_data, const char *callback_name, void (*callback) (xmlNode *, int, int, xmlNode *, void *)); gboolean cib_client_register_callback_full(cib_t *cib, int call_id, int timeout, gboolean only_success, void *user_data, const char *callback_name, void (*callback)(xmlNode *, int, int, xmlNode *, void *), void (*free_func)(void *)); int cib_process_query(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer); int cib_process_erase(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer); int cib_process_bump(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer); int cib_process_replace(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer); int cib_process_create(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer); int cib_process_modify(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer); int cib_process_delete(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer); int cib_process_diff(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer); int cib_process_upgrade(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer); /*! * \internal * \brief Query or modify a CIB * * \param[in] op PCMK__CIB_REQUEST_* operation to be performed * \param[in] options Flag set of \c cib_call_options * \param[in] section XPath to query or modify * \param[in] req unused * \param[in] input Portion of CIB to modify (used with * PCMK__CIB_REQUEST_CREATE, * PCMK__CIB_REQUEST_MODIFY, and * PCMK__CIB_REQUEST_REPLACE) - * \param[in] existing_cib Input CIB (used with PCMK__CIB_REQUEST_QUERY) + * \param[in,out] existing_cib Input CIB (used with PCMK__CIB_REQUEST_QUERY) * \param[in,out] result_cib CIB copy to make changes in (used with * PCMK__CIB_REQUEST_CREATE, * PCMK__CIB_REQUEST_MODIFY, * PCMK__CIB_REQUEST_DELETE, and * PCMK__CIB_REQUEST_REPLACE) * \param[out] answer Query result (used with PCMK__CIB_REQUEST_QUERY) * * \return Legacy Pacemaker return code */ -int cib_process_xpath(const char *op, int options, const char *section, xmlNode * req, - xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, - xmlNode ** answer); +int cib_process_xpath(const char *op, int options, const char *section, + const xmlNode *req, xmlNode *input, xmlNode *existing_cib, + xmlNode **result_cib, xmlNode ** answer); gboolean cib_config_changed(xmlNode * last, xmlNode * next, xmlNode ** diff); gboolean update_results(xmlNode * failed, xmlNode * target, const char *operation, int return_code); int cib_update_counter(xmlNode * xml_obj, const char *field, gboolean reset); int cib_internal_op(cib_t * cib, const char *op, const char *host, const char *section, xmlNode * data, xmlNode ** output_data, int call_options, const char *user_name); int cib_file_read_and_verify(const char *filename, const char *sigfile, xmlNode **root); int cib_file_write_with_digest(xmlNode *cib_root, const char *cib_dirname, const char *cib_filename); void cib__set_output(cib_t *cib, pcmk__output_t *out); cib_callback_client_t* cib__lookup_id (int call_id); /*! * \internal * \brief Connect to, query, and optionally disconnect from the CIB, returning * the resulting XML object. * * \param[out] cib If non-NULL, a pointer to where to store the CIB * connection. In this case, it is up to the caller to * disconnect from the CIB when finished. * \param[out] cib_object A pointer to where to store the XML query result. * * \return A standard Pacemaker return code */ int cib__signon_query(cib_t **cib, xmlNode **cib_object); int cib__clean_up_connection(cib_t **cib); int cib__update_node_attr(pcmk__output_t *out, cib_t *cib, int call_options, const char *section, const char *node_uuid, const char *set_type, const char *set_name, const char *attr_id, const char *attr_name, const char *attr_value, const char *user_name, const char *node_type); int cib__get_node_attrs(pcmk__output_t *out, cib_t *cib, const char *section, const char *node_uuid, const char *set_type, const char *set_name, const char *attr_id, const char *attr_name, const char *user_name, xmlNode **result); int cib__delete_node_attr(pcmk__output_t *out, cib_t *cib, int options, const char *section, const char *node_uuid, const char *set_type, const char *set_name, const char *attr_id, const char *attr_name, const char *attr_value, const char *user_name); #endif diff --git a/include/crm/common/cmdline_internal.h b/include/crm/common/cmdline_internal.h index ea8200d76f..db4fd59195 100644 --- a/include/crm/common/cmdline_internal.h +++ b/include/crm/common/cmdline_internal.h @@ -1,187 +1,186 @@ /* - * Copyright 2019-2021 the Pacemaker project contributors + * 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 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, GOptionEntry entries[]); +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, - GOptionEntry entries[]); + 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] argv The command line, typically as returned from - * pcmk__cmdline_preproc. + * \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, while glib will * try to handle like it is additional single letter arguments. glib * prefers -x 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 **argv, const char *special); +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/ipc.h b/include/crm/common/ipc.h index e6cb904c14..06866a43e6 100644 --- a/include/crm/common/ipc.h +++ b/include/crm/common/ipc.h @@ -1,233 +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 #include #include #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] api IPC API connection - * \param[in] event_type The type of event that occurred - * \param[in] status Event status - * \param[in] event_data Event-specific data - * \param[in] user_data Caller data provided when callback was registered + * \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(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(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 index 10db3fec1a..2c6713f403 100644 --- a/include/crm/common/ipc_attrd_internal.h +++ b/include/crm/common/ipc_attrd_internal.h @@ -1,195 +1,195 @@ /* * 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_IPC_ATTRD_INTERNAL__H # define PCMK__CRM_COMMON_IPC_ATTRD_INTERNAL__H #include // GList #include // 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] 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 + * \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] api Connection to pacemaker-attrd (or NULL to use - * a temporary new connection) - * \param[in] node Delete attribute for this node (or NULL for current node) - * \param[in] name Attribute name - * \param[in] options Bitmask of pcmk__node_attr_opts + * \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] api pacemaker-attrd IPC object - * \param[in] node Node to remove + * \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] api Connection to pacemaker-attrd - * \param[in] node Look up the attribute for this node - * (or NULL for all nodes) - * \param[in] name Attribute name - * \param[in] options Bitmask of pcmk__node_attr_opts + * \param[in,out] api Connection to pacemaker-attrd + * \param[in] node Look up the attribute for this node + * (or NULL for all nodes) + * \param[in] name Attribute name + * \param[in] options Bitmask of pcmk__node_attr_opts * * \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] api pacemaker-attrd IPC object - * \param[in] node Affect only this node (or NULL for all nodes) + * \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] 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 to choose first) - * \param[in] user_name ACL user to pass to pacemaker-attrd - * \param[in] options Bitmask of pcmk__node_attr_opts + * \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] api pacemaker-attrd IPC object - * \param[in] 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 to choose first) - * \param[in] user_name ACL user to pass to pacemaker-attrd - * \param[in] options Bitmask of pcmk__node_attr_opts + * \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_schedulerd.h b/include/crm/common/ipc_schedulerd.h index e43738e70e..303ec595b1 100644 --- a/include/crm/common/ipc_schedulerd.h +++ b/include/crm/common/ipc_schedulerd.h @@ -1,64 +1,64 @@ /* - * Copyright 2021 the Pacemaker project contributors + * 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 // 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] 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 + * \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/lists_internal.h b/include/crm/common/lists_internal.h index 8cdbe1075a..1de695a6e9 100644 --- a/include/crm/common/lists_internal.h +++ b/include/crm/common/lists_internal.h @@ -1,35 +1,36 @@ /* - * Copyright 2020 the Pacemaker project contributors + * 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 /*! * \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, GList *items, GCompareFunc cmp); +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 index 70fbcc45dc..fc58247ca5 100644 --- a/include/crm/common/logging.h +++ b/include/crm/common/logging.h @@ -1,393 +1,393 @@ /* * 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. */ #ifndef PCMK__CRM_COMMON_LOGGING__H # define PCMK__CRM_COMMON_LOGGING__H # include # include # include # include #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; enum xml_log_options { xml_log_option_filtered = 0x0001, xml_log_option_formatted = 0x0002, xml_log_option_text = 0x0004, /* add this option to dump text into xml */ xml_log_option_full_fledged = 0x0008, // Use libxml when converting XML to text 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, }; void crm_enable_blackbox(int nsig); void crm_disable_blackbox(int nsig); void crm_write_blackbox(int nsig, 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 **argv); +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); void log_data_element(int log_level, const char *file, const char *function, int line, const char *prefix, const xmlNode *data, int depth, gboolean formatted); /* returns the old value */ unsigned int set_crm_log_level(unsigned int level); unsigned int get_crm_log_level(void); /* * 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 // required by QB_LOG_INIT_DATA() macro # define CRM_TRACE_INIT_DATA(name) QB_LOG_INIT_DATA(name) #endif /* 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 * * \note This is a macro, and \p level may be evaluated more than once. */ # define do_crm_log(level, fmt, args...) do { \ 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 is a macro, and \p level may be evaluated more than once. * This does nothing when level is LOG_STDOUT. */ # define do_crm_log_unlikely(level, fmt, args...) do { \ 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 is a macro, and \p level may be evaluated more than once. * This does nothing when level is LOG_STDOUT. */ # define do_crm_log_xml(level, text, xml) do { \ switch (level) { \ case LOG_STDOUT: case LOG_NEVER: \ break; \ default: { \ static struct qb_log_callsite *xml_cs = NULL; \ 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)) { \ log_data_element((level), __FILE__, __func__, \ __LINE__, text, xml, 1, xml_log_option_formatted); \ } \ } \ 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 * * \note This is a macro, and \p level may be evaluated more than once. */ # define do_crm_log_alias(level, file, function, line, fmt, args...) do { \ 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 { \ switch (level) { \ case LOG_NEVER: \ break; \ default: { \ const char *err = strerror(errno); \ /* cast to int makes coverity happy when level == 0 */ \ if ((level) <= (int) crm_log_level) { \ fprintf(stderr, fmt ": %s (%d)\n" , ##args, err, errno);\ } \ 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 is a macro, and \p level may be evaluated more than once. * This does nothing when level is LOG_STDOUT. */ # define crm_log_tag(level, tag, fmt, args...) do { \ 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 #endif #ifdef __cplusplus } #endif #endif diff --git a/include/crm/common/mainloop.h b/include/crm/common/mainloop.h index 8d65170063..a55bcdf411 100644 --- a/include/crm/common/mainloop.h +++ b/include/crm/common/mainloop.h @@ -1,192 +1,192 @@ /* - * Copyright 2009-2021 the Pacemaker project contributors + * 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 // sighandler_t # include # include #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 # include 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] userdata User data passed when creating mainloop source + * \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] userdata User data passed when creating mainloop source + * \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] userdata User data passed when creating mainloop source + * \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 #endif #ifdef __cplusplus } #endif #endif diff --git a/include/crm/common/messages_internal.h b/include/crm/common/messages_internal.h index 2ba5bd9ee5..c33844dad7 100644 --- a/include/crm/common/messages_internal.h +++ b/include/crm/common/messages_internal.h @@ -1,122 +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 // uint32_t #include // xmlNode #include // pcmk__client_t #include // 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(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(pcmk__request_t *request) +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(pcmk__request_t *request) +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 index 495750ac2a..c035f30b3b 100644 --- a/include/crm/common/nvpair.h +++ b/include/crm/common/nvpair.h @@ -1,88 +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 // struct timeval # include // gpointer, gboolean, guint # include // xmlNode # include # 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(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(xmlNode *obj1, xmlNode *obj2, const char *element) +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/output_internal.h b/include/crm/common/output_internal.h index b8d1f0ed33..3a06b94e4e 100644 --- a/include/crm/common/output_internal.h +++ b/include/crm/common/output_internal.h @@ -1,910 +1,916 @@ /* * 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__OUTPUT_INTERNAL__H # define PCMK__OUTPUT_INTERNAL__H # include # include # include # include # include # include #ifdef __cplusplus extern "C" { #endif /** * \file * \brief Formatted output for pacemaker tools */ # define PCMK__API_VERSION "2.24" #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 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 The output functions structure. + * \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 The output functions structure. + * \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 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. + * \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. * * \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, GOptionEntry *options); + 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, pcmk__supported_format_t *table); +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, pcmk__message_entry_t *table); +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 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, int 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 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 be added as a child. */ void pcmk__output_xml_add_node(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] node The node to be added/ + * \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 node); +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 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 "": * * \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 The output functions structure. If NULL, any errors + * \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); #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 index bc634b1fa9..2bdd48f420 100644 --- a/include/crm/common/remote_internal.h +++ b/include/crm/common/remote_internal.h @@ -1,48 +1,48 @@ /* - * Copyright 2008-2021 the Pacemaker project contributors + * 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(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(void *sa, char *s); # ifdef HAVE_GNUTLS_GNUTLS_H # include 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(pcmk__client_t *client); /*! * \internal * \brief Perform client TLS handshake after establishing TCP socket * - * \param[in] remote Newly established remote connection - * \param[in] timeout_ms Abort if entire handshake is not complete within this + * \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/strings_internal.h b/include/crm/common/strings_internal.h index 4fdf3a5716..14322ded78 100644 --- a/include/crm/common/strings_internal.h +++ b/include/crm/common/strings_internal.h @@ -1,213 +1,213 @@ /* * 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 // bool #include // 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(char **list, size_t *len, 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] hash_table Table to modify - * \param[in] key Integer key of entry to remove + * \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, 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); 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(char **list, size_t *len, const char *word) { return pcmk__add_separated_word(list, len, 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/xml.h b/include/crm/common/xml.h index 5c7c784969..85ea35b9f2 100644 --- a/include/crm/common/xml.h +++ b/include/crm/common/xml.h @@ -1,317 +1,312 @@ /* * 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_XML__H # define PCMK__CRM_COMMON_XML__H # include # include # include # include # include # include # include # include # include # include #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, 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); int add_node_nocopy(xmlNode * parent, const char *name, xmlNode * child); /* * 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); /* Also dump the text node with xml_log_option_text enabled */ 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); void xml_log_changes(uint8_t level, const char *function, xmlNode *xml); void xml_log_patchset(uint8_t level, const char *function, 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); -/*! - * \brief xmlNode destructor which can be used in glib collections - */ -void crm_destroy_xml(gpointer data); - #if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1) #include #endif #ifdef __cplusplus } #endif #endif diff --git a/include/crm/common/xml_compat.h b/include/crm/common/xml_compat.h index 05a2ad3811..f36b43f3d2 100644 --- a/include/crm/common/xml_compat.h +++ b/include/crm/common/xml_compat.h @@ -1,53 +1,56 @@ /* * 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_XML_COMPAT__H # define PCMK__CRM_COMMON_XML_COMPAT__H #include // gboolean #include // xmlNode #include // 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 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 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 index cf5951b199..e2735a96bb 100644 --- a/include/crm/common/xml_internal.h +++ b/include/crm/common/xml_internal.h @@ -1,366 +1,369 @@ /* * 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__XML_INTERNAL__H # define PCMK__XML_INTERNAL__H /* * Internal-only wrappers for and extensions to libxml2 (libxslt) */ # include # include # include # include /* transitively imports qblog.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) /* 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(xmlNode *parent, const char *node_name, const char *attr_n, const char *attr_v); void pcmk__xe_remove_matching_attrs(xmlNode *element, bool (*match)(xmlAttrPtr, void *), void *user_data); GString *pcmk__element_xpath(const xmlNode *xml); /*! * \internal * \brief Get the root directory to scan XML artefacts of given kind for * * \param[in] ns governs the hierarchy nesting against the inherent root dir * * \return root directory to scan XML artefacts of given kind for */ char * pcmk__xml_artefact_root(enum pcmk__xml_artefact_ns ns); /*! * \internal * \brief Get the fully unwrapped path to particular XML artifact (RNG/XSLT) * * \param[in] ns denotes path forming details (parent dir, suffix) * \param[in] filespec symbolic file specification to be combined with * #artefact_ns to form the final path * \return unwrapped path to particular XML artifact (RNG/XSLT) */ char *pcmk__xml_artefact_path(enum pcmk__xml_artefact_ns ns, const char *filespec); /*! * \internal * \brief Return first non-text child node of an XML node * * \param[in] parent XML node to check * * \return First non-text child node of \p parent (or NULL if none) */ static inline xmlNode * pcmk__xml_first_child(const xmlNode *parent) { xmlNode *child = (parent? parent->children : NULL); while (child && (child->type == XML_TEXT_NODE)) { child = child->next; } return child; } /*! * \internal * \brief Return next non-text sibling node of an XML node * * \param[in] child XML node to check * * \return Next non-text sibling of \p child (or NULL if none) */ static inline xmlNode * pcmk__xml_next(const xmlNode *child) { xmlNode *next = (child? child->next : NULL); while (next && (next->type == XML_TEXT_NODE)) { next = next->next; } return next; } /*! * \internal * \brief Return first non-text child element of an XML node * * \param[in] parent XML node to check * * \return First child element of \p parent (or NULL if none) */ static inline xmlNode * pcmk__xe_first_child(const xmlNode *parent) { xmlNode *child = (parent? parent->children : NULL); while (child && (child->type != XML_ELEMENT_NODE)) { child = child->next; } return child; } /*! * \internal * \brief Return next non-text sibling element of an XML element * * \param[in] child XML element to check * * \return Next sibling element of \p child (or NULL if none) */ static inline xmlNode * pcmk__xe_next(const xmlNode *child) { xmlNode *next = child? child->next : NULL; while (next && (next->type != XML_ELEMENT_NODE)) { next = next->next; } return next; } /*! * \internal * \brief Like pcmk__xe_set_props, but takes a va_list instead of * arguments directly. + * + * \param[in,out] node XML to add attributes to + * \param[in] pairs NULL-terminated list of name/value pairs to add */ void pcmk__xe_set_propv(xmlNodePtr node, va_list pairs); /*! * \internal * \brief Add a NULL-terminated list of name/value pairs to the given * XML node as properties. * * \param[in,out] node XML node to add properties to * \param[in] ... NULL-terminated list of name/value pairs * * \note A NULL name terminates the arguments; a NULL value will be skipped. */ void pcmk__xe_set_props(xmlNodePtr node, ...) G_GNUC_NULL_TERMINATED; /*! * \internal * \brief Get first attribute of an XML element * * \param[in] xe XML element to check * * \return First attribute of \p xe (or NULL if \p xe is NULL or has none) */ static inline xmlAttr * pcmk__xe_first_attr(const xmlNode *xe) { return (xe == NULL)? NULL : xe->properties; } /*! * \internal * \brief Extract the ID attribute from an XML element * * \param[in] xpath String to search * \param[in] node Node to get the ID for * * \return ID attribute of \p node in xpath string \p xpath */ char * pcmk__xpath_node_id(const char *xpath, const char *node); /* internal XML-related utilities */ enum xml_private_flags { pcmk__xf_none = 0x0000, pcmk__xf_dirty = 0x0001, pcmk__xf_deleted = 0x0002, pcmk__xf_created = 0x0004, pcmk__xf_modified = 0x0008, pcmk__xf_tracking = 0x0010, pcmk__xf_processed = 0x0020, pcmk__xf_skip = 0x0040, pcmk__xf_moved = 0x0080, pcmk__xf_acl_enabled = 0x0100, pcmk__xf_acl_read = 0x0200, pcmk__xf_acl_write = 0x0400, pcmk__xf_acl_deny = 0x0800, pcmk__xf_acl_create = 0x1000, pcmk__xf_acl_denied = 0x2000, pcmk__xf_lazy = 0x4000, }; void pcmk__set_xml_doc_flag(xmlNode *xml, enum xml_private_flags flag); /*! * \internal * \brief Iterate over child elements of \p xml * * This function iterates over the children of \p xml, performing the * callback function \p handler on each node. If the callback returns * a value other than pcmk_rc_ok, the iteration stops and the value is * returned. It is therefore possible that not all children will be * visited. * * \param[in,out] xml The starting XML node. Can be NULL. * \param[in] child_element_name The name that the node must match in order * for \p handler to be run. If NULL, all * child elements will match. * \param[in] handler The callback function. * \param[in,out] userdata User data to pass to the callback function. * Can be NULL. * * \return Standard Pacemaker return code */ int pcmk__xe_foreach_child(xmlNode *xml, const char *child_element_name, int (*handler)(xmlNode *xml, void *userdata), void *userdata); #endif // PCMK__XML_INTERNAL__H diff --git a/include/crm/fencing/internal.h b/include/crm/fencing/internal.h index 9d931ae2d3..62dff031dc 100644 --- a/include/crm/fencing/internal.h +++ b/include/crm/fencing/internal.h @@ -1,222 +1,222 @@ /* * Copyright 2011-2022 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #ifndef STONITH_NG_INTERNAL__H # define STONITH_NG_INTERNAL__H # include # include # include # include # include enum st_device_flags { st_device_supports_none = 0x0000, st_device_supports_list = 0x0001, st_device_supports_status = 0x0002, st_device_supports_reboot = 0x0004, st_device_supports_parameter_plug = 0x0008, st_device_supports_parameter_port = 0x0010, st_device_supports_on = 0x0020, }; #define stonith__set_device_flags(device_flags, device_id, flags_to_set) do { \ device_flags = pcmk__set_flags_as(__func__, __LINE__, LOG_TRACE, \ "Fence device", device_id, \ (device_flags), (flags_to_set), \ #flags_to_set); \ } while (0) #define stonith__set_call_options(st_call_opts, call_for, flags_to_set) do { \ st_call_opts = pcmk__set_flags_as(__func__, __LINE__, LOG_TRACE, \ "Fencer call", (call_for), \ (st_call_opts), (flags_to_set), \ #flags_to_set); \ } while (0) #define stonith__clear_call_options(st_call_opts, call_for, flags_to_clear) do { \ st_call_opts = pcmk__clear_flags_as(__func__, __LINE__, LOG_TRACE, \ "Fencer call", (call_for), \ (st_call_opts), (flags_to_clear), \ #flags_to_clear); \ } while (0) struct stonith_action_s; typedef struct stonith_action_s stonith_action_t; stonith_action_t *stonith__action_create(const char *agent, const char *action_name, const char *target, uint32_t target_nodeid, int timeout_sec, GHashTable *device_args, GHashTable *port_map, const char *host_arg); void stonith__destroy_action(stonith_action_t *action); pcmk__action_result_t *stonith__action_result(stonith_action_t *action); int stonith__result2rc(const pcmk__action_result_t *result); void stonith__xe_set_result(xmlNode *xml, const pcmk__action_result_t *result); void stonith__xe_get_result(xmlNode *xml, pcmk__action_result_t *result); xmlNode *stonith__find_xe_with_result(xmlNode *xml); int stonith__execute_async(stonith_action_t *action, void *userdata, void (*done) (int pid, const pcmk__action_result_t *result, void *user_data), void (*fork_cb) (int pid, void *user_data)); int stonith__metadata_async(const char *agent, int timeout_sec, void (*callback)(int pid, const pcmk__action_result_t *result, void *user_data), void *user_data); xmlNode *create_level_registration_xml(const char *node, const char *pattern, const char *attr, const char *value, int level, - stonith_key_value_t *device_list); + const stonith_key_value_t *device_list); xmlNode *create_device_registration_xml(const char *id, enum stonith_namespace namespace, const char *agent, - stonith_key_value_t *params, + const stonith_key_value_t *params, const char *rsc_provides); void stonith__register_messages(pcmk__output_t *out); GList *stonith__parse_targets(const char *hosts); const char *stonith__later_succeeded(stonith_history_t *event, stonith_history_t *top_history); stonith_history_t *stonith__sort_history(stonith_history_t *history); void stonith__device_parameter_flags(uint32_t *device_flags, const char *device_name, xmlNode *metadata); # define ST_LEVEL_MAX 10 # define F_STONITH_CLIENTID "st_clientid" # define F_STONITH_CALLOPTS "st_callopt" # define F_STONITH_CALLID "st_callid" # define F_STONITH_CALLDATA "st_calldata" # define F_STONITH_OPERATION "st_op" # define F_STONITH_TARGET "st_target" # define F_STONITH_REMOTE_OP_ID "st_remote_op" # define F_STONITH_REMOTE_OP_ID_RELAY "st_remote_op_relay" # define F_STONITH_RC "st_rc" # define F_STONITH_OUTPUT "st_output" /*! Timeout period per a device execution */ # define F_STONITH_TIMEOUT "st_timeout" # define F_STONITH_TOLERANCE "st_tolerance" # define F_STONITH_DELAY "st_delay" /*! Action specific timeout period returned in query of fencing devices. */ # define F_STONITH_ACTION_TIMEOUT "st_action_timeout" /*! Host in query result is not allowed to run this action */ # define F_STONITH_ACTION_DISALLOWED "st_action_disallowed" /*! Maximum of random fencing delay for a device */ # define F_STONITH_DELAY_MAX "st_delay_max" /*! Base delay used for a fencing delay */ # define F_STONITH_DELAY_BASE "st_delay_base" /*! Has this device been verified using a monitor type * operation (monitor, list, status) */ # define F_STONITH_DEVICE_VERIFIED "st_monitor_verified" /*! device is required for this action */ # define F_STONITH_DEVICE_REQUIRED "st_required" /*! number of available devices in query result */ # define F_STONITH_AVAILABLE_DEVICES "st-available-devices" # define F_STONITH_CALLBACK_TOKEN "st_async_id" # define F_STONITH_CLIENTNAME "st_clientname" # define F_STONITH_CLIENTNODE "st_clientnode" # define F_STONITH_NOTIFY_ACTIVATE "st_notify_activate" # define F_STONITH_NOTIFY_DEACTIVATE "st_notify_deactivate" # define F_STONITH_DELEGATE "st_delegate" # define F_STONITH_DEVICE_SUPPORT_FLAGS "st_device_support_flags" /*! The node initiating the stonith operation. If an operation * is relayed, this is the last node the operation lands on. When * in standalone mode, origin is the client's id that originated the * operation. */ # define F_STONITH_ORIGIN "st_origin" # define F_STONITH_HISTORY_LIST "st_history" # define F_STONITH_DATE "st_date" # define F_STONITH_DATE_NSEC "st_date_nsec" # define F_STONITH_STATE "st_state" # define F_STONITH_ACTIVE "st_active" # define F_STONITH_DIFFERENTIAL "st_differential" # define F_STONITH_DEVICE "st_device_id" # define F_STONITH_ACTION "st_device_action" # define F_STONITH_MERGED "st_op_merged" # define T_STONITH_NG "stonith-ng" # define T_STONITH_REPLY "st-reply" /*! For async operations, an event from the server containing * the total amount of time the server is allowing for the operation * to take place is returned to the client. */ # define T_STONITH_TIMEOUT_VALUE "st-async-timeout-value" # define T_STONITH_NOTIFY "st_notify" # define STONITH_ATTR_ACTION_OP "action" # define STONITH_OP_EXEC "st_execute" # define STONITH_OP_TIMEOUT_UPDATE "st_timeout_update" # define STONITH_OP_QUERY "st_query" # define STONITH_OP_FENCE "st_fence" # define STONITH_OP_RELAY "st_relay" # define STONITH_OP_DEVICE_ADD "st_device_register" # define STONITH_OP_DEVICE_DEL "st_device_remove" # define STONITH_OP_FENCE_HISTORY "st_fence_history" # define STONITH_OP_LEVEL_ADD "st_level_add" # define STONITH_OP_LEVEL_DEL "st_level_remove" # define STONITH_WATCHDOG_AGENT "fence_watchdog" /* Don't change 2 below as it would break rolling upgrade */ # define STONITH_WATCHDOG_AGENT_INTERNAL "#watchdog" # define STONITH_WATCHDOG_ID "watchdog" stonith_history_t *stonith__first_matching_event(stonith_history_t *history, bool (*matching_fn)(stonith_history_t *, void *), void *user_data); bool stonith__event_state_pending(stonith_history_t *history, void *user_data); bool stonith__event_state_eq(stonith_history_t *history, void *user_data); bool stonith__event_state_neq(stonith_history_t *history, void *user_data); int stonith__legacy2status(int rc); int stonith__exit_status(stonith_callback_data_t *data); int stonith__execution_status(stonith_callback_data_t *data); const char *stonith__exit_reason(stonith_callback_data_t *data); int stonith__event_exit_status(stonith_event_t *event); int stonith__event_execution_status(stonith_event_t *event); const char *stonith__event_exit_reason(stonith_event_t *event); char *stonith__event_description(stonith_event_t *event); gchar *stonith__history_description(stonith_history_t *event, bool full_history, const char *later_succeeded, uint32_t show_opts); /*! * \internal * \brief Is a fencing operation in pending state? * * \param[in] state State as enum op_state value * * \return A boolean */ static inline bool stonith__op_state_pending(enum op_state state) { return state != st_failed && state != st_done; } gboolean stonith__watchdog_fencing_enabled_for_node(const char *node); gboolean stonith__watchdog_fencing_enabled_for_node_api(stonith_t *st, const char *node); #endif diff --git a/include/crm/stonith-ng.h b/include/crm/stonith-ng.h index ffc71653b4..fa87599aa4 100644 --- a/include/crm/stonith-ng.h +++ b/include/crm/stonith-ng.h @@ -1,595 +1,707 @@ /* * Copyright 2004-2022 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #ifndef PCMK__CRM_STONITH_NG__H # define PCMK__CRM_STONITH_NG__H #ifdef __cplusplus extern "C" { #endif /** * \file * \brief Fencing aka. STONITH * \ingroup fencing */ /* IMPORTANT: DLM source code includes this file directly, without having access * to other Pacemaker headers on its include path, so this file should *not* * include any other Pacemaker headers. (DLM might be updated to avoid the * issue, but we should still follow this guideline for a long time after.) */ # include # include # include // bool # include // uint32_t # include // time_t # define T_STONITH_NOTIFY_DISCONNECT "st_notify_disconnect" # define T_STONITH_NOTIFY_FENCE "st_notify_fence" # define T_STONITH_NOTIFY_HISTORY "st_notify_history" # define T_STONITH_NOTIFY_HISTORY_SYNCED "st_notify_history_synced" /* *INDENT-OFF* */ enum stonith_state { stonith_connected_command, stonith_connected_query, stonith_disconnected, }; enum stonith_call_options { st_opt_none = 0x00000000, st_opt_verbose = 0x00000001, st_opt_allow_suicide = 0x00000002, st_opt_manual_ack = 0x00000008, st_opt_discard_reply = 0x00000010, /* st_opt_all_replies = 0x00000020, */ st_opt_topology = 0x00000040, st_opt_scope_local = 0x00000100, st_opt_cs_nodeid = 0x00000200, st_opt_sync_call = 0x00001000, /*! Allow the timeout period for a callback to be adjusted * based on the time the server reports the operation will take. */ st_opt_timeout_updates = 0x00002000, /*! Only report back if operation is a success in callback */ st_opt_report_only_success = 0x00004000, /* used where ever apropriate - e.g. cleanup of history */ st_opt_cleanup = 0x000080000, /* used where ever apropriate - e.g. send out a history query to all nodes */ st_opt_broadcast = 0x000100000, }; /*! Order matters here, do not change values */ enum op_state { st_query, st_exec, st_done, st_duplicate, st_failed, }; // Supported fence agent interface standards enum stonith_namespace { st_namespace_invalid, st_namespace_any, st_namespace_internal, // Implemented internally by Pacemaker /* Neither of these projects are active any longer, but the fence agent * interfaces they created are still in use and supported by Pacemaker. */ st_namespace_rhcs, // Red Hat Cluster Suite compatible st_namespace_lha, // Linux-HA compatible }; enum stonith_namespace stonith_text2namespace(const char *namespace_s); const char *stonith_namespace2text(enum stonith_namespace st_namespace); enum stonith_namespace stonith_get_namespace(const char *agent, const char *namespace_s); typedef struct stonith_key_value_s { char *key; char *value; struct stonith_key_value_s *next; } stonith_key_value_t; typedef struct stonith_history_s { char *target; char *action; char *origin; char *delegate; char *client; int state; time_t completed; struct stonith_history_s *next; long completed_nsec; char *exit_reason; } stonith_history_t; typedef struct stonith_s stonith_t; typedef struct stonith_event_s { char *id; char *type; //!< \deprecated Will be removed in future release char *message; //!< \deprecated Will be removed in future release char *operation; int result; char *origin; char *target; char *action; char *executioner; char *device; /*! The name of the client that initiated the action. */ char *client_origin; //! \internal This field should be treated as internal to Pacemaker void *opaque; } stonith_event_t; typedef struct stonith_callback_data_s { int rc; int call_id; void *userdata; //! \internal This field should be treated as internal to Pacemaker void *opaque; } stonith_callback_data_t; typedef struct stonith_api_operations_s { /*! - * \brief Destroy the stonith api structure. + * \brief Destroy a fencer connection + * + * \param[in,out] st Fencer connection to destroy */ int (*free) (stonith_t *st); /*! - * \brief Connect to the local stonith daemon. + * \brief Connect to the local fencer + * + * \param[in,out] st Fencer connection to connect + * \param[in] name Client name to use + * \param[out] stonith_fd If NULL, use a main loop, otherwise + * store IPC file descriptor here * * \return Legacy Pacemaker return code */ int (*connect) (stonith_t *st, const char *name, int *stonith_fd); /*! * \brief Disconnect from the local stonith daemon. * + * \param[in,out] st Fencer connection to disconnect + * * \return Legacy Pacemaker return code */ int (*disconnect)(stonith_t *st); /*! * \brief Unregister a fence device with the local fencer * + * \param[in,out] st Fencer connection to disconnect + * \param[in] options Group of enum stonith_call_options + * \param[in] name ID of fence device to unregister + * * \return pcmk_ok (if synchronous) or positive call ID (if asynchronous) * on success, otherwise a negative legacy Pacemaker return code */ - int (*remove_device)( - stonith_t *st, int options, const char *name); + int (*remove_device)(stonith_t *st, int options, const char *name); /*! * \brief Register a fence device with the local fencer * + * \param[in,out] st Fencer connection to use + * \param[in] options Group of enum stonith_call_options + * \param[in] id ID of fence device to register + * \param[in] namespace Type of fence agent to search for ("redhat" + * or "stonith-ng" for RHCS-style, "internal" for + * Pacemaker-internal devices, "heartbeat" for + * LHA-style, or "any" or NULL for any) + * \param[in] agent Name of fence agent for device + * \param[in] params Fence agent parameters for device + * * \return pcmk_ok (if synchronous) or positive call ID (if asynchronous) * on success, otherwise a negative legacy Pacemaker return code */ - int (*register_device)( - stonith_t *st, int options, const char *id, - const char *provider, const char *agent, stonith_key_value_t *params); + int (*register_device)(stonith_t *st, int options, const char *id, + const char *namespace, const char *agent, + const stonith_key_value_t *params); /*! * \brief Unregister a fencing level for specified node with local fencer * + * \param[in,out] st Fencer connection to use + * \param[in] options Group of enum stonith_call_options + * \param[in] node Target node to unregister level for + * \param[in] level Topology level number to unregister + * * \return pcmk_ok (if synchronous) or positive call ID (if asynchronous) * on success, otherwise a negative legacy Pacemaker return code */ - int (*remove_level)( - stonith_t *st, int options, const char *node, int level); + int (*remove_level)(stonith_t *st, int options, const char *node, + int level); /*! * \brief Register a fencing level for specified node with local fencer * + * \param[in,out] st Fencer connection to use + * \param[in] options Group of enum stonith_call_options + * \param[in] node Target node to register level for + * \param[in] level Topology level number to register + * \param[in] device_list Devices to register in level + * * \return pcmk_ok (if synchronous) or positive call ID (if asynchronous) * on success, otherwise a negative legacy Pacemaker return code */ - int (*register_level)( - stonith_t *st, int options, const char *node, int level, stonith_key_value_t *device_list); + int (*register_level)(stonith_t *st, int options, const char *node, + int level, const stonith_key_value_t *device_list); /*! * \brief Retrieve a fence agent's metadata * * \param[in,out] stonith Fencer connection * \param[in] call_options Group of enum stonith_call_options * (currently ignored) * \param[in] agent Fence agent to query - * \param[in] namespace Namespace of fence agent to query (optional) + * \param[in] namespace Type of fence agent to search for ("redhat" + * or "stonith-ng" for RHCS-style, "internal" + * for Pacemaker-internal devices, "heartbeat" + * for LHA-style, or "any" or NULL for any) * \param[out] output Where to store metadata * \param[in] timeout_sec Error if not complete within this time * * \return Legacy Pacemaker return code * \note The caller is responsible for freeing *output using free(). */ int (*metadata)(stonith_t *stonith, int call_options, const char *agent, const char *namespace, char **output, int timeout_sec); /*! - * \brief Retrieve a list of installed stonith agents + * \brief Retrieve a list of installed fence agents * - * \note if provider is not provided, all known agents will be returned - * \note list must be freed using stonith_key_value_freeall() - * \note call_options parameter is not used, it is reserved for future use. + * \param[in,out] stonith Fencer connection to use + * \param[in] call_options Group of enum stonith_call_options + * (currently ignored) + * \param[in] namespace Type of fence agents to list ("redhat" + * or "stonith-ng" for RHCS-style, "internal" for + * Pacemaker-internal devices, "heartbeat" for + * LHA-style, or "any" or NULL for all) + * \param[out] devices Where to store agent list + * \param[in] timeout Error if unable to complete within this + * (currently ignored) * * \return Number of items in list on success, or negative errno otherwise + * \note The caller is responsible for freeing the returned list with + * stonith_key_value_freeall(). */ - int (*list_agents)(stonith_t *stonith, int call_options, const char *provider, - stonith_key_value_t **devices, int timeout); + int (*list_agents)(stonith_t *stonith, int call_options, + const char *namespace, stonith_key_value_t **devices, + int timeout); /*! - * \brief Retrieve string listing hosts and port assignments from a local stonith device. + * \brief Get the output of a fence device's list action + * + * \param[in,out] stonith Fencer connection to use + * \param[in] call_options Group of enum stonith_call_options + * \param[in] id Fence device ID to run list for + * \param[out] list_info Where to store list output + * \param[in] timeout Error if unable to complete within this * * \return pcmk_ok (if synchronous) or positive call ID (if asynchronous) * on success, otherwise a negative legacy Pacemaker return code */ - int (*list)(stonith_t *st, int options, const char *id, char **list_output, int timeout); + int (*list)(stonith_t *stonith, int call_options, const char *id, + char **list_info, int timeout); /*! - * \brief Check to see if a local stonith device is reachable + * \brief Check whether a fence device is reachable by monitor action + * + * \param[in,out] stonith Fencer connection to use + * \param[in] call_options Group of enum stonith_call_options + * \param[in] id Fence device ID to run monitor for + * \param[in] timeout Error if unable to complete within this * * \return pcmk_ok (if synchronous) or positive call ID (if asynchronous) * on success, otherwise a negative legacy Pacemaker return code */ - int (*monitor)(stonith_t *st, int options, const char *id, int timeout); + int (*monitor)(stonith_t *stonith, int call_options, const char *id, + int timeout); /*! - * \brief Check to see if a local stonith device's port is reachable + * \brief Check whether a fence device target is reachable by status action + * + * \param[in,out] stonith Fencer connection to use + * \param[in] call_options Group of enum stonith_call_options + * \param[in] id Fence device ID to run status for + * \param[in] port Fence target to run status for + * \param[in] timeout Error if unable to complete within this * * \return pcmk_ok (if synchronous) or positive call ID (if asynchronous) * on success, otherwise a negative legacy Pacemaker return code */ - int (*status)(stonith_t *st, int options, const char *id, const char *port, int timeout); + int (*status)(stonith_t *stonith, int call_options, const char *id, + const char *port, int timeout); /*! - * \brief Retrieve a list of registered stonith devices. + * \brief List registered fence devices + * + * \param[in,out] stonith Fencer connection to use + * \param[in] call_options Group of enum stonith_call_options + * \param[in] target Fence target to run status for + * \param[out] devices Where to store list of fence devices + * \param[in] timeout Error if unable to complete within this * * \note If node is provided, only devices that can fence the node id * will be returned. * * \return Number of items in list on success, or negative errno otherwise */ - int (*query)(stonith_t *st, int options, const char *node, - stonith_key_value_t **devices, int timeout); + int (*query)(stonith_t *stonith, int call_options, const char *target, + stonith_key_value_t **devices, int timeout); /*! - * \brief Issue a fencing action against a node. + * \brief Request that a target get fenced * - * \note Possible actions are, 'on', 'off', and 'reboot'. - * - * \param st, stonith connection - * \param options, call options - * \param node, The target node to fence - * \param action, The fencing action to take - * \param timeout, The default per device timeout to use with each device - * capable of fencing the target. + * \param[in,out] stonith Fencer connection to use + * \param[in] call_options Group of enum stonith_call_options + * \param[in] node Fence target + * \param[in] action "on", "off", or "reboot" + * \param[in] timeout Default per-device timeout to use with + * each executed device + * \param[in] tolerance Accept result of identical fence action + * completed within this time * * \return pcmk_ok (if synchronous) or positive call ID (if asynchronous) * on success, otherwise a negative legacy Pacemaker return code */ - int (*fence)(stonith_t *st, int options, const char *node, const char *action, - int timeout, int tolerance); + int (*fence)(stonith_t *stonith, int call_options, const char *node, + const char *action, int timeout, int tolerance); /*! - * \brief Manually confirm that a node is down. + * \brief Manually confirm that a node has been fenced + * + * \param[in,out] stonith Fencer connection to use + * \param[in] call_options Group of enum stonith_call_options + * \param[in] target Fence target * * \return pcmk_ok (if synchronous) or positive call ID (if asynchronous) * on success, otherwise a negative legacy Pacemaker return code */ - int (*confirm)(stonith_t *st, int options, const char *node); + int (*confirm)(stonith_t *stonith, int call_options, const char *target); /*! - * \brief Retrieve a list of fencing operations that have occurred for a specific node. + * \brief List fencing actions that have occurred for a target + * + * \param[in,out] stonith Fencer connection to use + * \param[in] call_options Group of enum stonith_call_options + * \param[in] node Fence target + * \param[out] history Where to store list of fencing actions + * \param[in] timeout Error if unable to complete within this * * \return Legacy Pacemaker return code */ - int (*history)(stonith_t *st, int options, const char *node, stonith_history_t **output, int timeout); + int (*history)(stonith_t *stonith, int call_options, const char *node, + stonith_history_t **history, int timeout); - int (*register_notification)( - stonith_t *st, const char *event, - void (*notify)(stonith_t *st, stonith_event_t *e)); + /*! + * \brief Register a callback for fence notifications + * + * \param[in,out] stonith Fencer connection to use + * \param[in] event Event to register for + * \param[in] callback Callback to register + * + * \return Legacy Pacemaker return code + */ + int (*register_notification)(stonith_t *stonith, const char *event, + void (*callback)(stonith_t *st, + stonith_event_t *e)); /*! - * \brief Remove a previously registered notification for \c event, or all - * notifications if NULL. + * \brief Unregister callbacks for fence notifications * - * \param[in] st Fencer connection to use - * \param[in] event The event to remove notifications for (may be NULL). + * \param[in,out] stonith Fencer connection to use + * \param[in] event Event to unregister callbacks for (NULL for all) * * \return Legacy Pacemaker return code */ - int (*remove_notification)(stonith_t *st, const char *event); + int (*remove_notification)(stonith_t *stonith, const char *event); /*! - * \brief Register a callback to receive the result of an asynchronous call - * - * \param[in] call_id The call ID to register callback for - * \param[in] timeout Default time to wait until callback expires - * \param[in] options Bitmask of \c stonith_call_options (respects - * \c st_opt_timeout_updates and - * \c st_opt_report_only_success) - * \param[in] userdata Pointer that will be given to callback - * \param[in] callback_name Unique name to identify callback - * \param[in] callback The callback function to register - * - * \return \c TRUE on success, \c FALSE if call_id is negative, -errno otherwise + * \brief Register a callback for an asynchronous fencing result + * + * \param[in,out] stonith Fencer connection to use + * \param[in] call_id Call ID to register callback for + * \param[in] timeout Error if result not received in this time + * \param[in] options Group of enum stonith_call_options + * (respects \c st_opt_timeout_updates and + * \c st_opt_report_only_success) + * \param[in,out] user_data Pointer to pass to callback + * \param[in] callback_name Unique identifier for callback + * \param[in] callback Callback to register (may be called + * immediately if \p call_id indicates error) + * + * \return \c TRUE on success, \c FALSE if call_id indicates error, + * or -EINVAL if \p stonith is not valid */ - int (*register_callback)(stonith_t *st, - int call_id, - int timeout, - int options, - void *userdata, - const char *callback_name, - void (*callback)(stonith_t *st, stonith_callback_data_t *data)); + int (*register_callback)(stonith_t *stonith, int call_id, int timeout, + int options, void *user_data, + const char *callback_name, + void (*callback)(stonith_t *st, + stonith_callback_data_t *data)); /*! - * \brief Remove a registered callback for a given call id + * \brief Unregister callbacks for asynchronous fencing results + * + * \param[in,out] stonith Fencer connection to use + * \param[in] call_id If \p all_callbacks is false, call ID + * to unregister callback for + * \param[in] all_callbacks If true, unregister all callbacks * * \return pcmk_ok */ - int (*remove_callback)(stonith_t *st, int call_id, bool all_callbacks); + int (*remove_callback)(stonith_t *stonith, int call_id, bool all_callbacks); /*! * \brief Unregister fencing level for specified node, pattern or attribute * - * \param[in] st Fencer connection to use - * \param[in] options Bitmask of stonith_call_options to pass to the fencer - * \param[in] node If not NULL, target level by this node name - * \param[in] pattern If not NULL, target by node name using this regex - * \param[in] attr If not NULL, target by this node attribute - * \param[in] value If not NULL, target by this node attribute value - * \param[in] level Index number of level to remove + * \param[in,out] st Fencer connection to use + * \param[in] options Group of enum stonith_call_options + * \param[in] node If not NULL, unregister level targeting this node + * \param[in] pattern If not NULL, unregister level targeting nodes + * whose names match this regular expression + * \param[in] attr If this and \p value are not NULL, unregister + * level targeting nodes with this node attribute + * set to \p value + * \param[in] value If this and \p attr are not NULL, unregister + * level targeting nodes with node attribute \p attr + * set to this + * \param[in] level Topology level number to remove * * \return pcmk_ok (if synchronous) or positive call ID (if asynchronous) * on success, otherwise a negative legacy Pacemaker return code - * - * \note The caller should set only one of node, pattern or attr/value. + * \note The caller should set only one of \p node, \p pattern, or \p attr + * and \p value. */ int (*remove_level_full)(stonith_t *st, int options, const char *node, const char *pattern, const char *attr, const char *value, int level); /*! * \brief Register fencing level for specified node, pattern or attribute * - * \param[in] st Fencer connection to use - * \param[in] options Bitmask of stonith_call_options to pass to fencer - * \param[in] node If not NULL, target level by this node name - * \param[in] pattern If not NULL, target by node name using this regex - * \param[in] attr If not NULL, target by this node attribute - * \param[in] value If not NULL, target by this node attribute value - * \param[in] level Index number of level to add - * \param[in] device_list Devices to use in level + * \param[in,out] st Fencer connection to use + * \param[in] options Group of enum stonith_call_options + * \param[in] node If not NULL, register level targeting this + * node by name + * \param[in] pattern If not NULL, register level targeting nodes + * whose names match this regular expression + * \param[in] attr If this and \p value are not NULL, register + * level targeting nodes with this node + * attribute set to \p value + * \param[in] value If this and \p attr are not NULL, register + * level targeting nodes with node attribute + * \p attr set to this + * \param[in] level Topology level number to remove + * \param[in] device_list Devices to use in level * * \return pcmk_ok (if synchronous) or positive call ID (if asynchronous) * on success, otherwise a negative legacy Pacemaker return code * * \note The caller should set only one of node, pattern or attr/value. */ int (*register_level_full)(stonith_t *st, int options, const char *node, const char *pattern, - const char *attr, const char *value, - int level, stonith_key_value_t *device_list); + const char *attr, const char *value, int level, + const stonith_key_value_t *device_list); /*! * \brief Validate an arbitrary stonith device configuration * - * \param[in] st Stonithd connection to use - * \param[in] call_options Bitmask of stonith_call_options to use with fencer - * \param[in] rsc_id ID used to replace CIB secrets in params - * \param[in] namespace_s Namespace of fence agent to validate (optional) - * \param[in] agent Fence agent to validate - * \param[in] params Configuration parameters to pass to fence agent - * \param[in] timeout Fail if no response within this many seconds - * \param[out] output If non-NULL, where to store any agent output - * \param[out] error_output If non-NULL, where to store agent error output + * \param[in,out] st Fencer connection to use + * \param[in] call_options Group of enum stonith_call_options + * \param[in] rsc_id ID used to replace CIB secrets in \p params + * \param[in] namespace_s Type of fence agent to validate ("redhat" + * or "stonith-ng" for RHCS-style, "internal" + * for Pacemaker-internal devices, "heartbeat" + * for LHA-style, or "any" or NULL for any) + * \param[in] agent Fence agent to validate + * \param[in] params Configuration parameters to pass to agent + * \param[in] timeout Fail if no response within this many seconds + * \param[out] output If non-NULL, where to store any agent output + * \param[out] error_output If non-NULL, where to store agent error output * * \return pcmk_ok if validation succeeds, -errno otherwise - * * \note If pcmk_ok is returned, the caller is responsible for freeing - * the output (if requested). + * the output (if requested) with free(). */ int (*validate)(stonith_t *st, int call_options, const char *rsc_id, const char *namespace_s, const char *agent, - stonith_key_value_t *params, int timeout, char **output, - char **error_output); + const stonith_key_value_t *params, int timeout, + char **output, char **error_output); /*! - * \brief Issue a fencing action against a node with requested fencing delay. + * \brief Request delayed fencing of a target * - * \note Possible actions are, 'on', 'off', and 'reboot'. - * - * \param st, stonith connection - * \param options, call options - * \param node, The target node to fence - * \param action, The fencing action to take - * \param timeout, The default per device timeout to use with each device - * capable of fencing the target. - * \param delay, Apply a fencing delay. Value -1 means disable also any - * static/random fencing delays from pcmk_delay_base/max + * \param[in,out] stonith Fencer connection to use + * \param[in] call_options Group of enum stonith_call_options + * \param[in] node Fence target + * \param[in] action "on", "off", or "reboot" + * \param[in] timeout Default per-device timeout to use with + * each executed device + * \param[in] tolerance Accept result of identical fence action + * completed within this time + * \param[in] delay Execute fencing after this delay (-1 + * disables any delay from pcmk_delay_base + * and pcmk_delay_max) * * \return pcmk_ok (if synchronous) or positive call ID (if asynchronous) * on success, otherwise a negative legacy Pacemaker return code */ - int (*fence_with_delay)(stonith_t *st, int options, const char *node, const char *action, - int timeout, int tolerance, int delay); + int (*fence_with_delay)(stonith_t *stonith, int call_options, + const char *node, const char *action, int timeout, + int tolerance, int delay); } stonith_api_operations_t; struct stonith_s { enum stonith_state state; int call_id; int call_timeout; //!< \deprecated Unused void *st_private; stonith_api_operations_t *cmds; }; /* *INDENT-ON* */ /* Core functions */ stonith_t *stonith_api_new(void); void stonith_api_delete(stonith_t * st); void stonith_dump_pending_callbacks(stonith_t * st); bool stonith_dispatch(stonith_t * st); stonith_key_value_t *stonith_key_value_add(stonith_key_value_t * kvp, const char *key, const char *value); void stonith_key_value_freeall(stonith_key_value_t * kvp, int keys, int values); void stonith_history_free(stonith_history_t *history); // Convenience functions int stonith_api_connect_retry(stonith_t *st, const char *name, int max_attempts); const char *stonith_op_state_str(enum op_state state); /* Basic helpers that allows nodes to be fenced and the history to be * queried without mainloop or the caller understanding the full API * * At least one of nodeid and uname are required */ int stonith_api_kick(uint32_t nodeid, const char *uname, int timeout, bool off); time_t stonith_api_time(uint32_t nodeid, const char *uname, bool in_progress); /* * Helpers for using the above functions without install-time dependencies * * Usage: * #include * * To turn a node off by corosync nodeid: * stonith_api_kick_helper(nodeid, 120, 1); * * To check the last fence date/time (also by nodeid): * last = stonith_api_time_helper(nodeid, 0); * * To check if fencing is in progress: * if(stonith_api_time_helper(nodeid, 1) > 0) { ... } * * eg. #include #include #include int main(int argc, char ** argv) { int rc = 0; int nodeid = 102; rc = stonith_api_time_helper(nodeid, 0); printf("%d last fenced at %s\n", nodeid, ctime(rc)); rc = stonith_api_kick_helper(nodeid, 120, 1); printf("%d fence result: %d\n", nodeid, rc); rc = stonith_api_time_helper(nodeid, 0); printf("%d last fenced at %s\n", nodeid, ctime(rc)); return 0; } */ # define STONITH_LIBRARY "libstonithd.so.26" typedef int (*st_api_kick_fn) (int nodeid, const char *uname, int timeout, bool off); typedef time_t (*st_api_time_fn) (int nodeid, const char *uname, bool in_progress); static inline int stonith_api_kick_helper(uint32_t nodeid, int timeout, bool off) { static void *st_library = NULL; static st_api_kick_fn st_kick_fn; if (st_library == NULL) { st_library = dlopen(STONITH_LIBRARY, RTLD_LAZY); } if (st_library && st_kick_fn == NULL) { st_kick_fn = (st_api_kick_fn) dlsym(st_library, "stonith_api_kick"); } if (st_kick_fn == NULL) { #ifdef ELIBACC return -ELIBACC; #else return -ENOSYS; #endif } return (*st_kick_fn) (nodeid, NULL, timeout, off); } static inline time_t stonith_api_time_helper(uint32_t nodeid, bool in_progress) { static void *st_library = NULL; static st_api_time_fn st_time_fn; if (st_library == NULL) { st_library = dlopen(STONITH_LIBRARY, RTLD_LAZY); } if (st_library && st_time_fn == NULL) { st_time_fn = (st_api_time_fn) dlsym(st_library, "stonith_api_time"); } if (st_time_fn == NULL) { return 0; } return (*st_time_fn) (nodeid, NULL, in_progress); } /** * Does the given agent describe a stonith resource that can exist? * * \param[in] agent What is the name of the agent? * \param[in] timeout Timeout to use when querying. If 0 is given, * use a default of 120. * * \return A boolean */ bool stonith_agent_exists(const char *agent, int timeout); /*! - * \brief Turn stonith action into a more readable string. + * \brief Turn fence action into a more readable string * - * \param action Stonith action + * \param[in] action Fence action */ const char *stonith_action_str(const char *action); #if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1) /* Normally we'd put this section in a separate file (crm/fencing/compat.h), but * we can't do that for the reason noted at the top of this file. That does mean * we have to duplicate these declarations where they're implemented. */ //! \deprecated Use stonith_get_namespace() instead const char *get_stonith_provider(const char *agent, const char *provider); #endif #ifdef __cplusplus } #endif #endif diff --git a/lib/cib/cib_ops.c b/lib/cib/cib_ops.c index ecf6a2af8f..c4674b2c42 100644 --- a/lib/cib/cib_ops.c +++ b/lib/cib/cib_ops.c @@ -1,858 +1,859 @@ /* * Copyright 2004-2022 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include int cib_process_query(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer) { xmlNode *obj_root = NULL; int result = pcmk_ok; crm_trace("Processing %s for %s section", op, pcmk__s(section, "unspecified")); if (options & cib_xpath) { return cib_process_xpath(op, options, section, req, input, existing_cib, result_cib, answer); } CRM_CHECK(*answer == NULL, free_xml(*answer)); *answer = NULL; if (pcmk__str_eq(XML_CIB_TAG_SECTION_ALL, section, pcmk__str_casei)) { section = NULL; } obj_root = pcmk_find_cib_element(existing_cib, section); if (obj_root == NULL) { result = -ENXIO; } else if (options & cib_no_children) { const char *tag = TYPE(obj_root); xmlNode *shallow = create_xml_node(*answer, tag); copy_in_properties(shallow, obj_root); *answer = shallow; } else { *answer = obj_root; } if (result == pcmk_ok && *answer == NULL) { crm_err("Error creating query response"); result = -ENOMSG; } return result; } int cib_process_erase(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer) { int result = pcmk_ok; crm_trace("Processing \"%s\" event", op); *answer = NULL; free_xml(*result_cib); *result_cib = createEmptyCib(0); copy_in_properties(*result_cib, existing_cib); cib_update_counter(*result_cib, XML_ATTR_GENERATION_ADMIN, FALSE); return result; } int cib_process_upgrade(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer) { int rc = 0; int new_version = 0; int current_version = 0; int max_version = 0; const char *max = crm_element_value(req, F_CIB_SCHEMA_MAX); const char *value = crm_element_value(existing_cib, XML_ATTR_VALIDATION); *answer = NULL; crm_trace("Processing \"%s\" event with max=%s", op, max); if (value != NULL) { current_version = get_schema_version(value); } if (max) { max_version = get_schema_version(max); } rc = update_validation(result_cib, &new_version, max_version, TRUE, !(options & cib_verbose)); if (new_version > current_version) { cib_update_counter(*result_cib, XML_ATTR_GENERATION_ADMIN, FALSE); cib_update_counter(*result_cib, XML_ATTR_GENERATION, TRUE); cib_update_counter(*result_cib, XML_ATTR_NUMUPDATES, TRUE); return pcmk_ok; } return rc; } int cib_process_bump(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer) { int result = pcmk_ok; crm_trace("Processing %s for epoch='%s'", op, pcmk__s(crm_element_value(existing_cib, XML_ATTR_GENERATION), "")); *answer = NULL; cib_update_counter(*result_cib, XML_ATTR_GENERATION, FALSE); return result; } int cib_update_counter(xmlNode * xml_obj, const char *field, gboolean reset) { char *new_value = NULL; char *old_value = NULL; int int_value = -1; if (reset == FALSE && crm_element_value(xml_obj, field) != NULL) { old_value = crm_element_value_copy(xml_obj, field); } if (old_value != NULL) { int_value = atoi(old_value); new_value = pcmk__itoa(++int_value); } else { new_value = strdup("1"); CRM_ASSERT(new_value != NULL); } crm_trace("Update %s from %s to %s", field, pcmk__s(old_value, "unset"), new_value); crm_xml_add(xml_obj, field, new_value); free(new_value); free(old_value); return pcmk_ok; } int cib_process_replace(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer) { const char *tag = NULL; int result = pcmk_ok; crm_trace("Processing %s for %s section", op, pcmk__s(section, "unspecified")); if (options & cib_xpath) { return cib_process_xpath(op, options, section, req, input, existing_cib, result_cib, answer); } *answer = NULL; if (input == NULL) { return -EINVAL; } tag = crm_element_name(input); if (pcmk__str_eq(XML_CIB_TAG_SECTION_ALL, section, pcmk__str_casei)) { section = NULL; } else if (pcmk__str_eq(tag, section, pcmk__str_casei)) { section = NULL; } if (pcmk__str_eq(tag, XML_TAG_CIB, pcmk__str_casei)) { int updates = 0; int epoch = 0; int admin_epoch = 0; int replace_updates = 0; int replace_epoch = 0; int replace_admin_epoch = 0; const char *reason = NULL; const char *peer = crm_element_value(req, F_ORIG); const char *digest = crm_element_value(req, XML_ATTR_DIGEST); if (digest) { const char *version = crm_element_value(req, XML_ATTR_CRM_VERSION); char *digest_verify = calculate_xml_versioned_digest(input, FALSE, TRUE, version ? version : CRM_FEATURE_SET); if (!pcmk__str_eq(digest_verify, digest, pcmk__str_casei)) { crm_err("Digest mis-match on replace from %s: %s vs. %s (expected)", peer, digest_verify, digest); reason = "digest mismatch"; } else { crm_info("Digest matched on replace from %s: %s", peer, digest); } free(digest_verify); } else { crm_trace("No digest to verify"); } cib_version_details(existing_cib, &admin_epoch, &epoch, &updates); cib_version_details(input, &replace_admin_epoch, &replace_epoch, &replace_updates); if (replace_admin_epoch < admin_epoch) { reason = XML_ATTR_GENERATION_ADMIN; } else if (replace_admin_epoch > admin_epoch) { /* no more checks */ } else if (replace_epoch < epoch) { reason = XML_ATTR_GENERATION; } else if (replace_epoch > epoch) { /* no more checks */ } else if (replace_updates < updates) { reason = XML_ATTR_NUMUPDATES; } if (reason != NULL) { crm_info("Replacement %d.%d.%d from %s not applied to %d.%d.%d:" " current %s is greater than the replacement", replace_admin_epoch, replace_epoch, replace_updates, peer, admin_epoch, epoch, updates, reason); result = -pcmk_err_old_data; } else { crm_info("Replaced %d.%d.%d with %d.%d.%d from %s", admin_epoch, epoch, updates, replace_admin_epoch, replace_epoch, replace_updates, peer); } free_xml(*result_cib); *result_cib = copy_xml(input); } else { xmlNode *obj_root = NULL; gboolean ok = TRUE; obj_root = pcmk_find_cib_element(*result_cib, section); ok = replace_xml_child(NULL, obj_root, input, FALSE); if (ok == FALSE) { crm_trace("No matching object to replace"); result = -ENXIO; } } return result; } int cib_process_delete(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer) { xmlNode *obj_root = NULL; crm_trace("Processing \"%s\" event", op); if (options & cib_xpath) { return cib_process_xpath(op, options, section, req, input, existing_cib, result_cib, answer); } if (input == NULL) { crm_err("Cannot perform modification with no data"); return -EINVAL; } obj_root = pcmk_find_cib_element(*result_cib, section); if(pcmk__str_eq(crm_element_name(input), section, pcmk__str_casei)) { xmlNode *child = NULL; for (child = pcmk__xml_first_child(input); child; child = pcmk__xml_next(child)) { if (replace_xml_child(NULL, obj_root, child, TRUE) == FALSE) { crm_trace("No matching object to delete: %s=%s", child->name, ID(child)); } } } else if (replace_xml_child(NULL, obj_root, input, TRUE) == FALSE) { crm_trace("No matching object to delete: %s=%s", input->name, ID(input)); } return pcmk_ok; } int cib_process_modify(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer) { xmlNode *obj_root = NULL; crm_trace("Processing \"%s\" event", op); if (options & cib_xpath) { return cib_process_xpath(op, options, section, req, input, existing_cib, result_cib, answer); } if (input == NULL) { crm_err("Cannot perform modification with no data"); return -EINVAL; } obj_root = pcmk_find_cib_element(*result_cib, section); if (obj_root == NULL) { xmlNode *tmp_section = NULL; const char *path = pcmk_cib_parent_name_for(section); if (path == NULL) { return -EINVAL; } tmp_section = create_xml_node(NULL, section); cib_process_xpath(PCMK__CIB_REQUEST_CREATE, 0, path, NULL, tmp_section, NULL, result_cib, answer); free_xml(tmp_section); obj_root = pcmk_find_cib_element(*result_cib, section); } CRM_CHECK(obj_root != NULL, return -EINVAL); if (update_xml_child(obj_root, input) == FALSE) { if (options & cib_can_create) { add_node_copy(obj_root, input); } else { return -ENXIO; } } if(options & cib_mixed_update) { int max = 0, lpc; xmlXPathObjectPtr xpathObj = xpath_search(*result_cib, "//@__delete__"); if (xpathObj) { max = numXpathResults(xpathObj); crm_log_xml_trace(*result_cib, "Mixed result"); } for (lpc = 0; lpc < max; lpc++) { xmlNode *match = getXpathResult(xpathObj, lpc); xmlChar *match_path = xmlGetNodePath(match); crm_debug("Destroying %s", match_path); free(match_path); free_xml(match); } freeXpathObject(xpathObj); } return pcmk_ok; } static int update_cib_object(xmlNode * parent, xmlNode * update) { int result = pcmk_ok; xmlNode *target = NULL; xmlNode *a_child = NULL; const char *replace = NULL; const char *object_id = NULL; const char *object_name = NULL; CRM_CHECK(update != NULL, return -EINVAL); CRM_CHECK(parent != NULL, return -EINVAL); object_name = crm_element_name(update); CRM_CHECK(object_name != NULL, return -EINVAL); object_id = ID(update); crm_trace("Processing update for <%s%s%s%s>", object_name, ((object_id == NULL)? "" : " id='"), pcmk__s(object_id, ""), ((object_id == NULL)? "" : "'")); if (object_id == NULL) { /* placeholder object */ target = find_xml_node(parent, object_name, FALSE); } else { target = pcmk__xe_match(parent, object_name, XML_ATTR_ID, object_id); } if (target == NULL) { target = create_xml_node(parent, object_name); } crm_trace("Found node <%s%s%s%s> to update", object_name, ((object_id == NULL)? "" : " id='"), pcmk__s(object_id, ""), ((object_id == NULL)? "" : "'")); replace = crm_element_value(update, XML_CIB_ATTR_REPLACE); if (replace != NULL) { xmlNode *remove = NULL; int last = 0, lpc = 0, len = 0; len = strlen(replace); while (lpc <= len) { if (replace[lpc] == ',' || replace[lpc] == 0) { char *replace_item = NULL; if (last == lpc) { /* nothing to do */ last = lpc + 1; goto incr; } replace_item = strndup(replace + last, lpc - last); remove = find_xml_node(target, replace_item, FALSE); if (remove != NULL) { crm_trace("Replacing node <%s> in <%s>", replace_item, crm_element_name(target)); free_xml(remove); remove = NULL; } free(replace_item); last = lpc + 1; } incr: lpc++; } xml_remove_prop(update, XML_CIB_ATTR_REPLACE); xml_remove_prop(target, XML_CIB_ATTR_REPLACE); } copy_in_properties(target, update); if (xml_acl_denied(target)) { crm_notice("Cannot update <%s id=%s>", pcmk__s(object_name, ""), pcmk__s(object_id, "")); return -EACCES; } crm_trace("Processing children of <%s%s%s%s>", object_name, ((object_id == NULL)? "" : " id='"), pcmk__s(object_id, ""), ((object_id == NULL)? "" : "'")); for (a_child = pcmk__xml_first_child(update); a_child != NULL; a_child = pcmk__xml_next(a_child)) { int tmp_result = 0; crm_trace("Updating child <%s%s%s%s>", crm_element_name(a_child), ((ID(a_child) == NULL)? "" : " id='"), pcmk__s(ID(a_child), ""), ((ID(a_child) == NULL)? "" : "'")); tmp_result = update_cib_object(target, a_child); /* only the first error is likely to be interesting */ if (tmp_result != pcmk_ok) { crm_err("Error updating child <%s%s%s%s>", crm_element_name(a_child), ((ID(a_child) == NULL)? "" : " id='"), pcmk__s(ID(a_child), ""), ((ID(a_child) == NULL)? "" : "'")); if (result == pcmk_ok) { result = tmp_result; } } } crm_trace("Finished handling update for <%s%s%s%s>", object_name, ((object_id == NULL)? "" : " id='"), pcmk__s(object_id, ""), ((object_id == NULL)? "" : "'")); return result; } static int add_cib_object(xmlNode * parent, xmlNode * new_obj) { const char *object_name = NULL; const char *object_id = NULL; xmlNode *equiv_node = NULL; if ((parent == NULL) || (new_obj == NULL)) { return -EINVAL; } object_name = crm_element_name(new_obj); if (object_name == NULL) { return -EINVAL; } object_id = ID(new_obj); crm_trace("Processing creation of <%s%s%s%s>", object_name, ((object_id == NULL)? "" : " id='"), pcmk__s(object_id, ""), ((object_id == NULL)? "" : "'")); if (object_id == NULL) { equiv_node = find_xml_node(parent, object_name, FALSE); } else { equiv_node = pcmk__xe_match(parent, object_name, XML_ATTR_ID, object_id); } if (equiv_node != NULL) { return -EEXIST; } return update_cib_object(parent, new_obj); } int cib_process_create(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer) { xmlNode *failed = NULL; int result = pcmk_ok; xmlNode *update_section = NULL; crm_trace("Processing %s for %s section", op, pcmk__s(section, "unspecified")); if (pcmk__str_eq(XML_CIB_TAG_SECTION_ALL, section, pcmk__str_casei)) { section = NULL; } else if (pcmk__str_eq(XML_TAG_CIB, section, pcmk__str_casei)) { section = NULL; } else if (pcmk__str_eq(crm_element_name(input), XML_TAG_CIB, pcmk__str_casei)) { section = NULL; } CRM_CHECK(strcmp(op, PCMK__CIB_REQUEST_CREATE) == 0, return -EINVAL); if (input == NULL) { crm_err("Cannot perform modification with no data"); return -EINVAL; } if (section == NULL) { return cib_process_modify(op, options, section, req, input, existing_cib, result_cib, answer); } failed = create_xml_node(NULL, XML_TAG_FAILED); update_section = pcmk_find_cib_element(*result_cib, section); if (pcmk__str_eq(crm_element_name(input), section, pcmk__str_casei)) { xmlNode *a_child = NULL; for (a_child = pcmk__xml_first_child(input); a_child != NULL; a_child = pcmk__xml_next(a_child)) { result = add_cib_object(update_section, a_child); if (update_results(failed, a_child, op, result)) { break; } } } else { result = add_cib_object(update_section, input); update_results(failed, input, op, result); } if ((result == pcmk_ok) && xml_has_children(failed)) { result = -EINVAL; } if (result != pcmk_ok) { crm_log_xml_err(failed, "CIB Update failures"); *answer = failed; } else { free_xml(failed); } return result; } int cib_process_diff(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer) { const char *originator = NULL; if (req != NULL) { originator = crm_element_value(req, F_ORIG); } crm_trace("Processing \"%s\" event from %s%s", op, originator, (pcmk_is_set(options, cib_force_diff)? " (global update)" : "")); free_xml(*result_cib); *result_cib = copy_xml(existing_cib); return xml_apply_patchset(*result_cib, input, TRUE); } gboolean cib_config_changed(xmlNode * last, xmlNode * next, xmlNode ** diff) { int lpc = 0, max = 0; gboolean config_changes = FALSE; xmlXPathObject *xpathObj = NULL; int format = 1; CRM_ASSERT(diff != NULL); if (*diff == NULL && last != NULL && next != NULL) { *diff = diff_xml_object(last, next, FALSE); } if (*diff == NULL) { goto done; } crm_element_value_int(*diff, "format", &format); /* This function only applies to v1 diffs. */ CRM_LOG_ASSERT(format == 1); xpathObj = xpath_search(*diff, "//" XML_CIB_TAG_CONFIGURATION); if (numXpathResults(xpathObj) > 0) { config_changes = TRUE; goto done; } freeXpathObject(xpathObj); /* * Do not check XML_TAG_DIFF_ADDED "//" XML_TAG_CIB * This always contains every field and would produce a false positive * every time if the checked value existed */ xpathObj = xpath_search(*diff, "//" XML_TAG_DIFF_REMOVED "//" XML_TAG_CIB); max = numXpathResults(xpathObj); for (lpc = 0; lpc < max; lpc++) { xmlNode *top = getXpathResult(xpathObj, lpc); if (crm_element_value(top, XML_ATTR_GENERATION) != NULL) { config_changes = TRUE; goto done; } if (crm_element_value(top, XML_ATTR_GENERATION_ADMIN) != NULL) { config_changes = TRUE; goto done; } if (crm_element_value(top, XML_ATTR_VALIDATION) != NULL) { config_changes = TRUE; goto done; } if (crm_element_value(top, XML_ATTR_CRM_VERSION) != NULL) { config_changes = TRUE; goto done; } if (crm_element_value(top, "remote-clear-port") != NULL) { config_changes = TRUE; goto done; } if (crm_element_value(top, "remote-tls-port") != NULL) { config_changes = TRUE; goto done; } } done: freeXpathObject(xpathObj); return config_changes; } int -cib_process_xpath(const char *op, int options, const char *section, xmlNode * req, xmlNode * input, - xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer) +cib_process_xpath(const char *op, int options, const char *section, + const xmlNode *req, xmlNode *input, xmlNode *existing_cib, + xmlNode **result_cib, xmlNode **answer) { int lpc = 0; int max = 0; int rc = pcmk_ok; bool is_query = pcmk__str_eq(op, PCMK__CIB_REQUEST_QUERY, pcmk__str_none); xmlXPathObjectPtr xpathObj = NULL; crm_trace("Processing \"%s\" event", op); if (is_query) { xpathObj = xpath_search(existing_cib, section); } else { xpathObj = xpath_search(*result_cib, section); } max = numXpathResults(xpathObj); if ((max < 1) && pcmk__str_eq(op, PCMK__CIB_REQUEST_DELETE, pcmk__str_none)) { crm_debug("%s was already removed", section); } else if (max < 1) { crm_debug("%s: %s does not exist", op, section); rc = -ENXIO; } else if (is_query) { if (max > 1) { *answer = create_xml_node(NULL, "xpath-query"); } } if (pcmk_is_set(options, cib_multiple) && pcmk__str_eq(op, PCMK__CIB_REQUEST_DELETE, pcmk__str_none)) { dedupXpathResults(xpathObj); } for (lpc = 0; lpc < max; lpc++) { xmlChar *path = NULL; xmlNode *match = getXpathResult(xpathObj, lpc); if (match == NULL) { continue; } path = xmlGetNodePath(match); crm_debug("Processing %s op for %s with %s", op, section, path); free(path); if (pcmk__str_eq(op, PCMK__CIB_REQUEST_DELETE, pcmk__str_none)) { if (match == *result_cib) { /* Attempting to delete the whole "/cib" */ crm_warn("Cannot perform %s for %s: The xpath is addressing the whole /cib", op, section); rc = -EINVAL; break; } free_xml(match); if ((options & cib_multiple) == 0) { break; } } else if (pcmk__str_eq(op, PCMK__CIB_REQUEST_MODIFY, pcmk__str_none)) { if (update_xml_child(match, input) == FALSE) { rc = -ENXIO; } else if ((options & cib_multiple) == 0) { break; } } else if (pcmk__str_eq(op, PCMK__CIB_REQUEST_CREATE, pcmk__str_none)) { add_node_copy(match, input); break; } else if (pcmk__str_eq(op, PCMK__CIB_REQUEST_QUERY, pcmk__str_none)) { if (options & cib_no_children) { const char *tag = TYPE(match); xmlNode *shallow = create_xml_node(*answer, tag); copy_in_properties(shallow, match); if (*answer == NULL) { *answer = shallow; } } else if (options & cib_xpath_address) { char *path = NULL; xmlNode *parent = match; while (parent && parent->type == XML_ELEMENT_NODE) { const char *id = crm_element_value(parent, XML_ATTR_ID); char *new_path = NULL; if (id) { new_path = crm_strdup_printf("/%s[@id='%s']%s", parent->name, id, (path? path : "")); } else { new_path = crm_strdup_printf("/%s%s", parent->name, (path? path : "")); } free(path); path = new_path; parent = parent->parent; } crm_trace("Got: %s", path); if (*answer == NULL) { *answer = create_xml_node(NULL, "xpath-query"); } parent = create_xml_node(*answer, "xpath-query-path"); crm_xml_add(parent, XML_ATTR_ID, path); free(path); } else if (*answer) { add_node_copy(*answer, match); } else { *answer = match; } } else if (pcmk__str_eq(op, PCMK__CIB_REQUEST_REPLACE, pcmk__str_none)) { xmlNode *parent = match->parent; free_xml(match); if (input != NULL) { add_node_copy(parent, input); } if ((options & cib_multiple) == 0) { break; } } } freeXpathObject(xpathObj); return rc; } /* remove this function */ gboolean update_results(xmlNode * failed, xmlNode * target, const char *operation, int return_code) { xmlNode *xml_node = NULL; gboolean was_error = FALSE; const char *error_msg = NULL; if (return_code != pcmk_ok) { error_msg = pcmk_strerror(return_code); was_error = TRUE; xml_node = create_xml_node(failed, XML_FAIL_TAG_CIB); add_node_copy(xml_node, target); crm_xml_add(xml_node, XML_FAILCIB_ATTR_ID, ID(target)); crm_xml_add(xml_node, XML_FAILCIB_ATTR_OBJTYPE, TYPE(target)); crm_xml_add(xml_node, XML_FAILCIB_ATTR_OP, operation); crm_xml_add(xml_node, XML_FAILCIB_ATTR_REASON, error_msg); crm_warn("Action %s failed: %s (cde=%d)", operation, error_msg, return_code); } return was_error; } diff --git a/lib/common/cmdline.c b/lib/common/cmdline.c index 41bf0c49f4..d34a071304 100644 --- a/lib/common/cmdline.c +++ b/lib/common/cmdline.c @@ -1,371 +1,371 @@ /* - * Copyright 2019-2021 the Pacemaker project contributors + * 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. */ #include #include #include #include #include #include #include static gboolean bump_verbosity(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { pcmk__common_args_t *common_args = (pcmk__common_args_t *) data; common_args->verbosity++; return TRUE; } pcmk__common_args_t * pcmk__new_common_args(const char *summary) { pcmk__common_args_t *args = NULL; args = calloc(1, sizeof(pcmk__common_args_t)); if (args == NULL) { crm_exit(CRM_EX_OSERR); } args->summary = strdup(summary); if (args->summary == NULL) { free(args); args = NULL; crm_exit(CRM_EX_OSERR); } return args; } static void free_common_args(gpointer data) { pcmk__common_args_t *common_args = (pcmk__common_args_t *) data; free(common_args->summary); free(common_args->output_ty); free(common_args->output_dest); if (common_args->output_as_descr != NULL) { free(common_args->output_as_descr); } free(common_args); } GOptionContext * pcmk__build_arg_context(pcmk__common_args_t *common_args, const char *fmts, GOptionGroup **output_group, const char *param_string) { char *desc = crm_strdup_printf("Report bugs to %s\n", PACKAGE_BUGREPORT); GOptionContext *context; GOptionGroup *main_group; GOptionEntry main_entries[3] = { { "version", '$', 0, G_OPTION_ARG_NONE, &(common_args->version), "Display software version and exit", NULL }, { "verbose", 'V', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, bump_verbosity, "Increase debug output (may be specified multiple times)", NULL }, { NULL } }; main_group = g_option_group_new(NULL, "Application Options:", NULL, common_args, free_common_args); g_option_group_add_entries(main_group, main_entries); context = g_option_context_new(param_string); g_option_context_set_summary(context, common_args->summary); g_option_context_set_description(context, desc); g_option_context_set_main_group(context, main_group); if (fmts != NULL) { GOptionEntry output_entries[3] = { { "output-as", 0, 0, G_OPTION_ARG_STRING, &(common_args->output_ty), NULL, "FORMAT" }, { "output-to", 0, 0, G_OPTION_ARG_STRING, &(common_args->output_dest), "Specify file name for output (or \"-\" for stdout)", "DEST" }, { NULL } }; if (*output_group == NULL) { *output_group = g_option_group_new("output", "Output Options:", "Show output help", NULL, NULL); } common_args->output_as_descr = crm_strdup_printf("Specify output format as one of: %s", fmts); output_entries[0].description = common_args->output_as_descr; g_option_group_add_entries(*output_group, output_entries); g_option_context_add_group(context, *output_group); } free(desc); // main_group is now owned by context, we don't free it here // cppcheck-suppress memleak return context; } void pcmk__free_arg_context(GOptionContext *context) { if (context == NULL) { return; } g_option_context_free(context); } void -pcmk__add_main_args(GOptionContext *context, GOptionEntry entries[]) +pcmk__add_main_args(GOptionContext *context, const GOptionEntry entries[]) { GOptionGroup *main_group = g_option_context_get_main_group(context); g_option_group_add_entries(main_group, entries); } void pcmk__add_arg_group(GOptionContext *context, const char *name, const char *header, const char *desc, - GOptionEntry entries[]) + const GOptionEntry entries[]) { GOptionGroup *group = NULL; group = g_option_group_new(name, header, desc, NULL, NULL); g_option_group_add_entries(group, entries); g_option_context_add_group(context, group); // group is now owned by context, we don't free it here // cppcheck-suppress memleak } static gchar * string_replace(gchar *str, const gchar *sub, const gchar *repl) { /* This function just replaces all occurrences of a substring * with some other string. It doesn't handle cases like overlapping, * so don't get clever with it. * * FIXME: When glib >= 2.68 is supported, we can get rid of this * function and use g_string_replace instead. */ gchar **split = g_strsplit(str, sub, 0); gchar *retval = g_strjoinv(repl, split); g_strfreev(split); return retval; } gchar * pcmk__quote_cmdline(gchar **argv) { GString *gs = NULL; gchar *retval = NULL; if (argv == NULL || argv[0] == NULL) { return NULL; } gs = g_string_sized_new(100); for (int i = 0; argv[i] != NULL; i++) { if (i > 0) { g_string_append(gs, " "); } if (strchr(argv[i], ' ') == NULL) { /* The arg does not contain a space. */ g_string_append_printf(gs, "%s", argv[i]); } else if (strchr(argv[i], '\'') == NULL) { /* The arg contains a space, but not a single quote. */ g_string_append_printf(gs, "'%s'", argv[i]); } else { /* The arg contains both a space and a single quote, which needs to * be replaced with an escaped version. We do this instead of counting * on libxml to handle the escaping for various reasons: * * (1) This keeps the string as valid shell. * (2) We don't want to use XML entities in formats besides XML and HTML. * (3) The string we are feeding to libxml is something like: "a b 'c d' e". * It won't escape the single quotes around 'c d' here because there is * no need to escape quotes inside a different form of quote. If we * change the string to "a b 'c'd' e", we haven't changed anything - it's * still single quotes inside double quotes. * * On the other hand, if we replace the single quote with "'", then * we have introduced an ampersand which libxml will escape. This leaves * us with "&apos;" which is not what we want. * * It's simplest to just escape with a backslash. */ gchar *repl = string_replace(argv[i], "'", "\\\'"); g_string_append_printf(gs, "'%s'", repl); g_free(repl); } } retval = gs->str; g_string_free(gs, FALSE); return retval; } gchar ** -pcmk__cmdline_preproc(char **argv, const char *special) { +pcmk__cmdline_preproc(char *const *argv, const char *special) { GPtrArray *arr = NULL; bool saw_dash_dash = false; bool copy_option = false; if (argv == NULL) { return NULL; } if (g_get_prgname() == NULL && argv && *argv) { gchar *basename = g_path_get_basename(*argv); g_set_prgname(basename); g_free(basename); } arr = g_ptr_array_new(); for (int i = 0; argv[i] != NULL; i++) { /* If this is the first time we saw "--" in the command line, set * a flag so we know to just copy everything after it over. We also * want to copy the "--" over so whatever actually parses the command * line when we're done knows where arguments end. */ if (saw_dash_dash == false && strcmp(argv[i], "--") == 0) { saw_dash_dash = true; } if (saw_dash_dash == true) { g_ptr_array_add(arr, g_strdup(argv[i])); continue; } if (copy_option == true) { g_ptr_array_add(arr, g_strdup(argv[i])); copy_option = false; continue; } /* This is just a dash by itself. That could indicate stdin/stdout, or * it could be user error. Copy it over and let glib figure it out. */ if (pcmk__str_eq(argv[i], "-", pcmk__str_casei)) { g_ptr_array_add(arr, g_strdup(argv[i])); continue; } /* This is a short argument, or perhaps several. Iterate over it * and explode them out into individual arguments. */ if (g_str_has_prefix(argv[i], "-") && !g_str_has_prefix(argv[i], "--")) { /* Skip over leading dash */ - char *ch = argv[i]+1; + const char *ch = argv[i]+1; /* This looks like the start of a number, which means it is a negative * number. It's probably the argument to the preceeding option, but * we can't know that here. Copy it over and let whatever handles * arguments next figure it out. */ if (*ch != '\0' && *ch >= '1' && *ch <= '9') { bool is_numeric = true; while (*ch != '\0') { if (!isdigit(*ch)) { is_numeric = false; break; } ch++; } if (is_numeric) { g_ptr_array_add(arr, g_strdup_printf("%s", argv[i])); continue; } else { /* This argument wasn't entirely numeric. Reset ch to the * beginning so we can process it one character at a time. */ ch = argv[i]+1; } } while (*ch != '\0') { /* This is a special short argument that takes an option. getopt * allows values to be interspersed with a list of arguments, but * glib does not. Grab both the argument and its value and * separate them into a new argument. */ if (special != NULL && strchr(special, *ch) != NULL) { /* The argument does not occur at the end of this string of * arguments. Take everything through the end as its value. */ if (*(ch+1) != '\0') { g_ptr_array_add(arr, g_strdup_printf("-%c", *ch)); g_ptr_array_add(arr, g_strdup(ch+1)); break; /* The argument occurs at the end of this string. Hopefully * whatever comes next in argv is its value. It may not be, * but that is not for us to decide. */ } else { g_ptr_array_add(arr, g_strdup_printf("-%c", *ch)); copy_option = true; ch++; } /* This is a regular short argument. Just copy it over. */ } else { g_ptr_array_add(arr, g_strdup_printf("-%c", *ch)); ch++; } } /* This is a long argument, or an option, or something else. * Copy it over - everything else is copied, so this keeps it easy for * the caller to know what to do with the memory when it's done. */ } else { g_ptr_array_add(arr, g_strdup(argv[i])); } } g_ptr_array_add(arr, NULL); return (char **) g_ptr_array_free(arr, FALSE); } G_GNUC_PRINTF(3, 4) gboolean pcmk__force_args(GOptionContext *context, GError **error, const char *format, ...) { int len = 0; char *buf = NULL; gchar **extra_args = NULL; va_list ap; gboolean retval = TRUE; va_start(ap, format); len = vasprintf(&buf, format, ap); CRM_ASSERT(len > 0); va_end(ap); if (!g_shell_parse_argv(buf, NULL, &extra_args, error)) { g_strfreev(extra_args); free(buf); return FALSE; } retval = g_option_context_parse_strv(context, &extra_args, error); g_strfreev(extra_args); free(buf); return retval; } diff --git a/lib/common/lists.c b/lib/common/lists.c index f9668eeee2..9cd88d73c0 100644 --- a/lib/common/lists.c +++ b/lib/common/lists.c @@ -1,27 +1,27 @@ /* * Copyright 2004-2022 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #include #include GList* -pcmk__subtract_lists(GList *from, GList *items, GCompareFunc cmp) +pcmk__subtract_lists(GList *from, const GList *items, GCompareFunc cmp) { GList *result = g_list_copy(from); - for (GList *item = items; item != NULL; item = item->next) { + for (const GList *item = items; item != NULL; item = item->next) { GList *match = g_list_find_custom(result, item->data, cmp); if (match != NULL) { result = g_list_remove(result, match->data); } } return result; } diff --git a/lib/common/logging.c b/lib/common/logging.c index 9b3e9d5805..5143809697 100644 --- a/lib/common/logging.c +++ b/lib/common/logging.c @@ -1,1120 +1,1120 @@ /* * Copyright 2004-2022 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Use high-resolution (millisecond) timestamps if libqb supports them #ifdef QB_FEATURE_LOG_HIRES_TIMESTAMPS #define TIMESTAMP_FORMAT_SPEC "%%T" typedef struct timespec *log_time_t; #else #define TIMESTAMP_FORMAT_SPEC "%%t" typedef time_t log_time_t; #endif unsigned int crm_log_level = LOG_INFO; unsigned int crm_trace_nonlog = 0; bool pcmk__is_daemon = false; static unsigned int crm_log_priority = LOG_NOTICE; static GLogFunc glib_log_default = NULL; static gboolean crm_tracing_enabled(void); static void crm_glib_handler(const gchar * log_domain, GLogLevelFlags flags, const gchar * message, gpointer user_data) { int log_level = LOG_WARNING; GLogLevelFlags msg_level = (flags & G_LOG_LEVEL_MASK); static struct qb_log_callsite *glib_cs = NULL; if (glib_cs == NULL) { glib_cs = qb_log_callsite_get(__func__, __FILE__, "glib-handler", LOG_DEBUG, __LINE__, crm_trace_nonlog); } switch (msg_level) { case G_LOG_LEVEL_CRITICAL: log_level = LOG_CRIT; if (crm_is_callsite_active(glib_cs, LOG_DEBUG, 0) == FALSE) { /* log and record how we got here */ crm_abort(__FILE__, __func__, __LINE__, message, TRUE, TRUE); } break; case G_LOG_LEVEL_ERROR: log_level = LOG_ERR; break; case G_LOG_LEVEL_MESSAGE: log_level = LOG_NOTICE; break; case G_LOG_LEVEL_INFO: log_level = LOG_INFO; break; case G_LOG_LEVEL_DEBUG: log_level = LOG_DEBUG; break; case G_LOG_LEVEL_WARNING: case G_LOG_FLAG_RECURSION: case G_LOG_FLAG_FATAL: case G_LOG_LEVEL_MASK: log_level = LOG_WARNING; break; } do_crm_log(log_level, "%s: %s", log_domain, message); } #ifndef NAME_MAX # define NAME_MAX 256 #endif /*! * \internal * \brief Write out a blackbox (enabling blackboxes if needed) * * \param[in] nsig Signal number that was received * * \note This is a true signal handler, and so must be async-safe. */ static void crm_trigger_blackbox(int nsig) { if(nsig == SIGTRAP) { /* Turn it on if it wasn't already */ crm_enable_blackbox(nsig); } crm_write_blackbox(nsig, NULL); } void crm_log_deinit(void) { if (glib_log_default != NULL) { g_log_set_default_handler(glib_log_default, NULL); } } #define FMT_MAX 256 /*! * \internal * \brief Set the log format string based on the passed-in method * * \param[in] method The detail level of the log output * \param[in] daemon The daemon ID included in error messages * \param[in] use_pid Cached result of getpid() call, for efficiency * \param[in] use_nodename Cached result of uname() call, for efficiency * */ /* XXX __attribute__((nonnull)) for use_nodename parameter */ static void set_format_string(int method, const char *daemon, pid_t use_pid, const char *use_nodename) { if (method == QB_LOG_SYSLOG) { // The system log gets a simplified, user-friendly format crm_extended_logging(method, QB_FALSE); qb_log_format_set(method, "%g %p: %b"); } else { // Everything else gets more detail, for advanced troubleshooting int offset = 0; char fmt[FMT_MAX]; if (method > QB_LOG_STDERR) { // If logging to file, prefix with timestamp, node name, daemon ID offset += snprintf(fmt + offset, FMT_MAX - offset, TIMESTAMP_FORMAT_SPEC " %s %-20s[%lu] ", use_nodename, daemon, (unsigned long) use_pid); } // Add function name (in parentheses) offset += snprintf(fmt + offset, FMT_MAX - offset, "(%%n"); if (crm_tracing_enabled()) { // When tracing, add file and line number offset += snprintf(fmt + offset, FMT_MAX - offset, "@%%f:%%l"); } offset += snprintf(fmt + offset, FMT_MAX - offset, ")"); // Add tag (if any), severity, and actual message offset += snprintf(fmt + offset, FMT_MAX - offset, " %%g\t%%p: %%b"); CRM_LOG_ASSERT(offset > 0); qb_log_format_set(method, fmt); } } #define DEFAULT_LOG_FILE CRM_LOG_DIR "/pacemaker.log" static bool logfile_disabled(const char *filename) { return pcmk__str_eq(filename, PCMK__VALUE_NONE, pcmk__str_casei) || pcmk__str_eq(filename, "/dev/null", pcmk__str_none); } /*! * \internal * \brief Fix log file ownership if group is wrong or doesn't have access * * \param[in] filename Log file name (for logging only) * \param[in] logfd Log file descriptor * * \return Standard Pacemaker return code */ static int chown_logfile(const char *filename, int logfd) { uid_t pcmk_uid = 0; gid_t pcmk_gid = 0; struct stat st; int rc; // Get the log file's current ownership and permissions if (fstat(logfd, &st) < 0) { return errno; } // Any other errors don't prevent file from being used as log rc = pcmk_daemon_user(&pcmk_uid, &pcmk_gid); if (rc != pcmk_ok) { rc = pcmk_legacy2rc(rc); crm_warn("Not changing '%s' ownership because user information " "unavailable: %s", filename, pcmk_rc_str(rc)); return pcmk_rc_ok; } if ((st.st_gid == pcmk_gid) && ((st.st_mode & S_IRWXG) == (S_IRGRP|S_IWGRP))) { return pcmk_rc_ok; } if (fchown(logfd, pcmk_uid, pcmk_gid) < 0) { crm_warn("Couldn't change '%s' ownership to user %s gid %d: %s", filename, CRM_DAEMON_USER, pcmk_gid, strerror(errno)); } return pcmk_rc_ok; } // Reset log file permissions (using environment variable if set) static void chmod_logfile(const char *filename, int logfd) { const char *modestr = getenv("PCMK_logfile_mode"); mode_t filemode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP; if (modestr != NULL) { long filemode_l = strtol(modestr, NULL, 8); if ((filemode_l != LONG_MIN) && (filemode_l != LONG_MAX)) { filemode = (mode_t) filemode_l; } } if ((filemode != 0) && (fchmod(logfd, filemode) < 0)) { crm_warn("Couldn't change '%s' mode to %04o: %s", filename, filemode, strerror(errno)); } } // If we're root, correct a log file's permissions if needed static int set_logfile_permissions(const char *filename, FILE *logfile) { if (geteuid() == 0) { int logfd = fileno(logfile); int rc = chown_logfile(filename, logfd); if (rc != pcmk_rc_ok) { return rc; } chmod_logfile(filename, logfd); } return pcmk_rc_ok; } // Enable libqb logging to a new log file static void enable_logfile(int fd) { qb_log_ctl(fd, QB_LOG_CONF_ENABLED, QB_TRUE); #if 0 qb_log_ctl(fd, QB_LOG_CONF_FILE_SYNC, 1); // Turn on synchronous writes #endif #ifdef HAVE_qb_log_conf_QB_LOG_CONF_MAX_LINE_LEN // Longer than default, for logging long XML lines qb_log_ctl(fd, QB_LOG_CONF_MAX_LINE_LEN, 800); #endif crm_update_callsites(); } static inline void disable_logfile(int fd) { qb_log_ctl(fd, QB_LOG_CONF_ENABLED, QB_FALSE); } static void setenv_logfile(const char *filename) { // Some resource agents will log only if environment variable is set if (pcmk__env_option(PCMK__ENV_LOGFILE) == NULL) { pcmk__set_env_option(PCMK__ENV_LOGFILE, filename); } } /*! * \brief Add a file to be used as a Pacemaker detail log * * \param[in] filename Name of log file to use * * \return Standard Pacemaker return code */ int pcmk__add_logfile(const char *filename) { /* No log messages from this function will be logged to the new log! * If another target such as syslog has already been added, the messages * should show up there. */ int fd = 0; int rc = pcmk_rc_ok; FILE *logfile = NULL; bool is_default = false; static int default_fd = -1; static bool have_logfile = false; // Use default if caller didn't specify (and we don't already have one) if (filename == NULL) { if (have_logfile) { return pcmk_rc_ok; } filename = DEFAULT_LOG_FILE; } // If the user doesn't want logging, we're done if (logfile_disabled(filename)) { return pcmk_rc_ok; } // If the caller wants the default and we already have it, we're done is_default = pcmk__str_eq(filename, DEFAULT_LOG_FILE, pcmk__str_none); if (is_default && (default_fd >= 0)) { return pcmk_rc_ok; } // Check whether we have write access to the file logfile = fopen(filename, "a"); if (logfile == NULL) { rc = errno; crm_warn("Logging to '%s' is disabled: %s " CRM_XS " uid=%u gid=%u", filename, strerror(rc), geteuid(), getegid()); return rc; } rc = set_logfile_permissions(filename, logfile); if (rc != pcmk_rc_ok) { crm_warn("Logging to '%s' is disabled: %s " CRM_XS " permissions", filename, strerror(rc)); fclose(logfile); return rc; } // Close and reopen as libqb logging target fclose(logfile); fd = qb_log_file_open(filename); if (fd < 0) { crm_warn("Logging to '%s' is disabled: %s " CRM_XS " qb_log_file_open", filename, strerror(-fd)); return -fd; // == +errno } if (is_default) { default_fd = fd; setenv_logfile(filename); } else if (default_fd >= 0) { crm_notice("Switching logging to %s", filename); disable_logfile(default_fd); } crm_notice("Additional logging available in %s", filename); enable_logfile(fd); have_logfile = true; return pcmk_rc_ok; } static int blackbox_trigger = 0; static volatile char *blackbox_file_prefix = NULL; static void blackbox_logger(int32_t t, struct qb_log_callsite *cs, log_time_t timestamp, const char *msg) { if(cs && cs->priority < LOG_ERR) { crm_write_blackbox(SIGTRAP, cs); /* Bypass the over-dumping logic */ } else { crm_write_blackbox(0, cs); } } static void crm_control_blackbox(int nsig, bool enable) { int lpc = 0; if (blackbox_file_prefix == NULL) { pid_t pid = getpid(); blackbox_file_prefix = crm_strdup_printf("%s/%s-%lu", CRM_BLACKBOX_DIR, crm_system_name, (unsigned long) pid); } if (enable && qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_STATE_GET, 0) != QB_LOG_STATE_ENABLED) { qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_SIZE, 5 * 1024 * 1024); /* Any size change drops existing entries */ qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_ENABLED, QB_TRUE); /* Setting the size seems to disable it */ /* Enable synchronous logging */ for (lpc = QB_LOG_BLACKBOX; lpc < QB_LOG_TARGET_MAX; lpc++) { qb_log_ctl(lpc, QB_LOG_CONF_FILE_SYNC, QB_TRUE); } crm_notice("Initiated blackbox recorder: %s", blackbox_file_prefix); /* Save to disk on abnormal termination */ crm_signal_handler(SIGSEGV, crm_trigger_blackbox); crm_signal_handler(SIGABRT, crm_trigger_blackbox); crm_signal_handler(SIGILL, crm_trigger_blackbox); crm_signal_handler(SIGBUS, crm_trigger_blackbox); crm_signal_handler(SIGFPE, crm_trigger_blackbox); crm_update_callsites(); blackbox_trigger = qb_log_custom_open(blackbox_logger, NULL, NULL, NULL); qb_log_ctl(blackbox_trigger, QB_LOG_CONF_ENABLED, QB_TRUE); crm_trace("Trigger: %d is %d %d", blackbox_trigger, qb_log_ctl(blackbox_trigger, QB_LOG_CONF_STATE_GET, 0), QB_LOG_STATE_ENABLED); crm_update_callsites(); } else if (!enable && qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_STATE_GET, 0) == QB_LOG_STATE_ENABLED) { qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_ENABLED, QB_FALSE); /* Disable synchronous logging again when the blackbox is disabled */ for (lpc = QB_LOG_BLACKBOX; lpc < QB_LOG_TARGET_MAX; lpc++) { qb_log_ctl(lpc, QB_LOG_CONF_FILE_SYNC, QB_FALSE); } } } void crm_enable_blackbox(int nsig) { crm_control_blackbox(nsig, TRUE); } void crm_disable_blackbox(int nsig) { crm_control_blackbox(nsig, FALSE); } /*! * \internal * \brief Write out a blackbox, if blackboxes are enabled * * \param[in] nsig Signal that was received * \param[in] cs libqb callsite * * \note This may be called via a true signal handler and so must be async-safe. * @TODO actually make this async-safe */ void crm_write_blackbox(int nsig, struct qb_log_callsite *cs) { static volatile int counter = 1; static volatile time_t last = 0; char buffer[NAME_MAX]; time_t now = time(NULL); if (blackbox_file_prefix == NULL) { return; } switch (nsig) { case 0: case SIGTRAP: /* The graceful case - such as assertion failure or user request */ if (nsig == 0 && now == last) { /* Prevent over-dumping */ return; } snprintf(buffer, NAME_MAX, "%s.%d", blackbox_file_prefix, counter++); if (nsig == SIGTRAP) { crm_notice("Blackbox dump requested, please see %s for contents", buffer); } else if (cs) { syslog(LOG_NOTICE, "Problem detected at %s:%d (%s), please see %s for additional details", cs->function, cs->lineno, cs->filename, buffer); } else { crm_notice("Problem detected, please see %s for additional details", buffer); } last = now; qb_log_blackbox_write_to_file(buffer); /* Flush the existing contents * A size change would also work */ qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_ENABLED, QB_FALSE); qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_ENABLED, QB_TRUE); break; default: /* Do as little as possible, just try to get what we have out * We logged the filename when the blackbox was enabled */ crm_signal_handler(nsig, SIG_DFL); qb_log_blackbox_write_to_file((const char *)blackbox_file_prefix); qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_ENABLED, QB_FALSE); raise(nsig); break; } } static const char * crm_quark_to_string(uint32_t tag) { const char *text = g_quark_to_string(tag); if (text) { return text; } return ""; } static void crm_log_filter_source(int source, const char *trace_files, const char *trace_fns, const char *trace_fmts, const char *trace_tags, const char *trace_blackbox, struct qb_log_callsite *cs) { if (qb_log_ctl(source, QB_LOG_CONF_STATE_GET, 0) != QB_LOG_STATE_ENABLED) { return; } else if (cs->tags != crm_trace_nonlog && source == QB_LOG_BLACKBOX) { /* Blackbox gets everything if enabled */ qb_bit_set(cs->targets, source); } else if (source == blackbox_trigger && blackbox_trigger > 0) { /* Should this log message result in the blackbox being dumped */ if (cs->priority <= LOG_ERR) { qb_bit_set(cs->targets, source); } else if (trace_blackbox) { char *key = crm_strdup_printf("%s:%d", cs->function, cs->lineno); if (strstr(trace_blackbox, key) != NULL) { qb_bit_set(cs->targets, source); } free(key); } } else if (source == QB_LOG_SYSLOG) { /* No tracing to syslog */ if (cs->priority <= crm_log_priority && cs->priority <= crm_log_level) { qb_bit_set(cs->targets, source); } /* Log file tracing options... */ } else if (cs->priority <= crm_log_level) { qb_bit_set(cs->targets, source); } else if (trace_files && strstr(trace_files, cs->filename) != NULL) { qb_bit_set(cs->targets, source); } else if (trace_fns && strstr(trace_fns, cs->function) != NULL) { qb_bit_set(cs->targets, source); } else if (trace_fmts && strstr(trace_fmts, cs->format) != NULL) { qb_bit_set(cs->targets, source); } else if (trace_tags && cs->tags != 0 && cs->tags != crm_trace_nonlog && g_quark_to_string(cs->tags) != NULL) { qb_bit_set(cs->targets, source); } } static void crm_log_filter(struct qb_log_callsite *cs) { int lpc = 0; static int need_init = 1; static const char *trace_fns = NULL; static const char *trace_tags = NULL; static const char *trace_fmts = NULL; static const char *trace_files = NULL; static const char *trace_blackbox = NULL; if (need_init) { need_init = 0; trace_fns = getenv("PCMK_trace_functions"); trace_fmts = getenv("PCMK_trace_formats"); trace_tags = getenv("PCMK_trace_tags"); trace_files = getenv("PCMK_trace_files"); trace_blackbox = getenv("PCMK_trace_blackbox"); if (trace_tags != NULL) { uint32_t tag; char token[500]; const char *offset = NULL; const char *next = trace_tags; do { offset = next; next = strchrnul(offset, ','); snprintf(token, sizeof(token), "%.*s", (int)(next - offset), offset); tag = g_quark_from_string(token); crm_info("Created GQuark %u from token '%s' in '%s'", tag, token, trace_tags); if (next[0] != 0) { next++; } } while (next != NULL && next[0] != 0); } } cs->targets = 0; /* Reset then find targets to enable */ for (lpc = QB_LOG_SYSLOG; lpc < QB_LOG_TARGET_MAX; lpc++) { crm_log_filter_source(lpc, trace_files, trace_fns, trace_fmts, trace_tags, trace_blackbox, cs); } } gboolean crm_is_callsite_active(struct qb_log_callsite *cs, uint8_t level, uint32_t tags) { gboolean refilter = FALSE; if (cs == NULL) { return FALSE; } if (cs->priority != level) { cs->priority = level; refilter = TRUE; } if (cs->tags != tags) { cs->tags = tags; refilter = TRUE; } if (refilter) { crm_log_filter(cs); } if (cs->targets == 0) { return FALSE; } return TRUE; } void crm_update_callsites(void) { static gboolean log = TRUE; if (log) { log = FALSE; crm_debug ("Enabling callsites based on priority=%d, files=%s, functions=%s, formats=%s, tags=%s", crm_log_level, getenv("PCMK_trace_files"), getenv("PCMK_trace_functions"), getenv("PCMK_trace_formats"), getenv("PCMK_trace_tags")); } qb_log_filter_fn_set(crm_log_filter); } static gboolean crm_tracing_enabled(void) { if (crm_log_level == LOG_TRACE) { return TRUE; } else if (getenv("PCMK_trace_files") || getenv("PCMK_trace_functions") || getenv("PCMK_trace_formats") || getenv("PCMK_trace_tags")) { return TRUE; } return FALSE; } static int crm_priority2int(const char *name) { struct syslog_names { const char *name; int priority; }; static struct syslog_names p_names[] = { {"emerg", LOG_EMERG}, {"alert", LOG_ALERT}, {"crit", LOG_CRIT}, {"error", LOG_ERR}, {"warning", LOG_WARNING}, {"notice", LOG_NOTICE}, {"info", LOG_INFO}, {"debug", LOG_DEBUG}, {NULL, -1} }; int lpc; for (lpc = 0; name != NULL && p_names[lpc].name != NULL; lpc++) { if (pcmk__str_eq(p_names[lpc].name, name, pcmk__str_none)) { return p_names[lpc].priority; } } return crm_log_priority; } /*! * \internal * \brief Set the identifier for the current process * * If the identifier crm_system_name is not already set, then it is set as follows: * - it is passed to the function via the "entity" parameter, or * - it is derived from the executable name * * The identifier can be used in logs, IPC, and more. * * This method also sets the PCMK_service environment variable. * * \param[in] entity If not NULL, will be assigned to the identifier * \param[in] argc The number of command line parameters * \param[in] argv The command line parameter values */ static void -set_identity(const char *entity, int argc, char **argv) +set_identity(const char *entity, int argc, char *const *argv) { if (crm_system_name != NULL) { return; // Already set, don't overwrite } if (entity != NULL) { crm_system_name = strdup(entity); } else if ((argc > 0) && (argv != NULL)) { char *mutable = strdup(argv[0]); char *modified = basename(mutable); if (strstr(modified, "lt-") == modified) { modified += 3; } crm_system_name = strdup(modified); free(mutable); } else { crm_system_name = strdup("Unknown"); } CRM_ASSERT(crm_system_name != NULL); setenv("PCMK_service", crm_system_name, 1); } void -crm_log_preinit(const char *entity, int argc, char **argv) +crm_log_preinit(const char *entity, int argc, char *const *argv) { /* Configure libqb logging with nothing turned on */ struct utsname res; int lpc = 0; int32_t qb_facility = 0; pid_t pid = getpid(); const char *nodename = "localhost"; static bool have_logging = false; if (have_logging) { return; } have_logging = true; crm_xml_init(); /* Sets buffer allocation strategy */ if (crm_trace_nonlog == 0) { crm_trace_nonlog = g_quark_from_static_string("Pacemaker non-logging tracepoint"); } umask(S_IWGRP | S_IWOTH | S_IROTH); /* Redirect messages from glib functions to our handler */ glib_log_default = g_log_set_default_handler(crm_glib_handler, NULL); /* and for good measure... - this enum is a bit field (!) */ g_log_set_always_fatal((GLogLevelFlags) 0); /*value out of range */ /* Set crm_system_name, which is used as the logging name. It may also * be used for other purposes such as an IPC client name. */ set_identity(entity, argc, argv); qb_facility = qb_log_facility2int("local0"); qb_log_init(crm_system_name, qb_facility, LOG_ERR); crm_log_level = LOG_CRIT; /* Nuke any syslog activity until it's asked for */ qb_log_ctl(QB_LOG_SYSLOG, QB_LOG_CONF_ENABLED, QB_FALSE); #ifdef HAVE_qb_log_conf_QB_LOG_CONF_MAX_LINE_LEN // Shorter than default, generous for what we *should* send to syslog qb_log_ctl(QB_LOG_SYSLOG, QB_LOG_CONF_MAX_LINE_LEN, 256); #endif if (uname(memset(&res, 0, sizeof(res))) == 0 && *res.nodename != '\0') { nodename = res.nodename; } /* Set format strings and disable threading * Pacemaker and threads do not mix well (due to the amount of forking) */ qb_log_tags_stringify_fn_set(crm_quark_to_string); for (lpc = QB_LOG_SYSLOG; lpc < QB_LOG_TARGET_MAX; lpc++) { qb_log_ctl(lpc, QB_LOG_CONF_THREADED, QB_FALSE); #ifdef HAVE_qb_log_conf_QB_LOG_CONF_ELLIPSIS // End truncated lines with '...' qb_log_ctl(lpc, QB_LOG_CONF_ELLIPSIS, QB_TRUE); #endif set_format_string(lpc, crm_system_name, pid, nodename); } #ifdef ENABLE_NLS /* Enable translations (experimental). Currently we only have a few * proof-of-concept translations for some option help. The goal would be to * offer translations for option help and man pages rather than logs or * documentation, to reduce the burden of maintaining them. */ // Load locale information for the local host from the environment setlocale(LC_ALL, ""); // Tell gettext where to find Pacemaker message catalogs CRM_ASSERT(bindtextdomain(PACKAGE, PCMK__LOCALE_DIR) != NULL); // Tell gettext to use the Pacemaker message catalogs CRM_ASSERT(textdomain(PACKAGE) != NULL); // Tell gettext that the translated strings are stored in UTF-8 bind_textdomain_codeset(PACKAGE, "UTF-8"); #endif } gboolean crm_log_init(const char *entity, uint8_t level, gboolean daemon, gboolean to_stderr, int argc, char **argv, gboolean quiet) { const char *syslog_priority = NULL; const char *facility = pcmk__env_option(PCMK__ENV_LOGFACILITY); const char *f_copy = facility; pcmk__is_daemon = daemon; crm_log_preinit(entity, argc, argv); if (level > LOG_TRACE) { level = LOG_TRACE; } if(level > crm_log_level) { crm_log_level = level; } /* Should we log to syslog */ if (facility == NULL) { if (pcmk__is_daemon) { facility = "daemon"; } else { facility = PCMK__VALUE_NONE; } pcmk__set_env_option(PCMK__ENV_LOGFACILITY, facility); } if (pcmk__str_eq(facility, PCMK__VALUE_NONE, pcmk__str_casei)) { quiet = TRUE; } else { qb_log_ctl(QB_LOG_SYSLOG, QB_LOG_CONF_FACILITY, qb_log_facility2int(facility)); } if (pcmk__env_option_enabled(crm_system_name, PCMK__ENV_DEBUG)) { /* Override the default setting */ crm_log_level = LOG_DEBUG; } /* What lower threshold do we have for sending to syslog */ syslog_priority = pcmk__env_option(PCMK__ENV_LOGPRIORITY); if (syslog_priority) { crm_log_priority = crm_priority2int(syslog_priority); } qb_log_filter_ctl(QB_LOG_SYSLOG, QB_LOG_FILTER_ADD, QB_LOG_FILTER_FILE, "*", crm_log_priority); // Log to syslog unless requested to be quiet if (!quiet) { qb_log_ctl(QB_LOG_SYSLOG, QB_LOG_CONF_ENABLED, QB_TRUE); } /* Should we log to stderr */ if (pcmk__env_option_enabled(crm_system_name, PCMK__ENV_STDERR)) { /* Override the default setting */ to_stderr = TRUE; } crm_enable_stderr(to_stderr); // Log to a file if we're a daemon or user asked for one { const char *logfile = pcmk__env_option(PCMK__ENV_LOGFILE); if (!pcmk__str_eq(PCMK__VALUE_NONE, logfile, pcmk__str_casei) && (pcmk__is_daemon || (logfile != NULL))) { // Daemons always get a log file, unless explicitly set to "none" pcmk__add_logfile(logfile); } } if (pcmk__is_daemon && pcmk__env_option_enabled(crm_system_name, PCMK__ENV_BLACKBOX)) { crm_enable_blackbox(0); } /* Summary */ crm_trace("Quiet: %d, facility %s", quiet, f_copy); pcmk__env_option(PCMK__ENV_LOGFILE); pcmk__env_option(PCMK__ENV_LOGFACILITY); crm_update_callsites(); /* Ok, now we can start logging... */ // Disable daemon request if user isn't root or Pacemaker daemon user if (pcmk__is_daemon) { const char *user = getenv("USER"); if (user != NULL && !pcmk__strcase_any_of(user, "root", CRM_DAEMON_USER, NULL)) { crm_trace("Not switching to corefile directory for %s", user); pcmk__is_daemon = false; } } if (pcmk__is_daemon) { int user = getuid(); struct passwd *pwent = getpwuid(user); if (pwent == NULL) { crm_perror(LOG_ERR, "Cannot get name for uid: %d", user); } else if (!pcmk__strcase_any_of(pwent->pw_name, "root", CRM_DAEMON_USER, NULL)) { crm_trace("Don't change active directory for regular user: %s", pwent->pw_name); } else if (chdir(CRM_CORE_DIR) < 0) { crm_perror(LOG_INFO, "Cannot change active directory to " CRM_CORE_DIR); } else { crm_info("Changed active directory to " CRM_CORE_DIR); } /* Original meanings from signal(7) * * Signal Value Action Comment * SIGTRAP 5 Core Trace/breakpoint trap * SIGUSR1 30,10,16 Term User-defined signal 1 * SIGUSR2 31,12,17 Term User-defined signal 2 * * Our usage is as similar as possible */ mainloop_add_signal(SIGUSR1, crm_enable_blackbox); mainloop_add_signal(SIGUSR2, crm_disable_blackbox); mainloop_add_signal(SIGTRAP, crm_trigger_blackbox); } else if (!quiet) { crm_log_args(argc, argv); } return TRUE; } /* returns the old value */ unsigned int set_crm_log_level(unsigned int level) { unsigned int old = crm_log_level; if (level > LOG_TRACE) { level = LOG_TRACE; } crm_log_level = level; crm_update_callsites(); crm_trace("New log level: %d", level); return old; } void crm_enable_stderr(int enable) { if (enable && qb_log_ctl(QB_LOG_STDERR, QB_LOG_CONF_STATE_GET, 0) != QB_LOG_STATE_ENABLED) { qb_log_ctl(QB_LOG_STDERR, QB_LOG_CONF_ENABLED, QB_TRUE); crm_update_callsites(); } else if (enable == FALSE) { qb_log_ctl(QB_LOG_STDERR, QB_LOG_CONF_ENABLED, QB_FALSE); } } /*! * \brief Make logging more verbose * * If logging to stderr is not already enabled when this function is called, * enable it. Otherwise, increase the log level by 1. * * \param[in] argc Ignored * \param[in] argv Ignored */ void crm_bump_log_level(int argc, char **argv) { if (qb_log_ctl(QB_LOG_STDERR, QB_LOG_CONF_STATE_GET, 0) != QB_LOG_STATE_ENABLED) { crm_enable_stderr(TRUE); } else { set_crm_log_level(crm_log_level + 1); } } unsigned int get_crm_log_level(void) { return crm_log_level; } /*! * \brief Log the command line (once) * * \param[in] Number of values in \p argv * \param[in] Command-line arguments (including command name) * * \note This function will only log once, even if called with different * arguments. */ void crm_log_args(int argc, char **argv) { static bool logged = false; gchar *arg_string = NULL; if ((argc == 0) || (argv == NULL) || logged) { return; } logged = true; arg_string = g_strjoinv(" ", argv); crm_notice("Invoked: %s", arg_string); g_free(arg_string); } void crm_log_output_fn(const char *file, const char *function, int line, int level, const char *prefix, const char *output) { const char *next = NULL; const char *offset = NULL; if (level == LOG_NEVER) { return; } if (output == NULL) { if (level != LOG_STDOUT) { level = LOG_TRACE; } output = "-- empty --"; } next = output; do { offset = next; next = strchrnul(offset, '\n'); do_crm_log_alias(level, file, function, line, "%s [ %.*s ]", prefix, (int)(next - offset), offset); if (next[0] != 0) { next++; } } while (next != NULL && next[0] != 0); } void pcmk__cli_init_logging(const char *name, unsigned int verbosity) { crm_log_init(name, LOG_ERR, FALSE, FALSE, 0, NULL, TRUE); for (int i = 0; i < verbosity; i++) { /* These arguments are ignored, so pass placeholders. */ crm_bump_log_level(0, NULL); } } // Deprecated functions kept only for backward API compatibility // LCOV_EXCL_START #include gboolean crm_log_cli_init(const char *entity) { pcmk__cli_init_logging(entity, 0); return TRUE; } gboolean crm_add_logfile(const char *filename) { return pcmk__add_logfile(filename) == pcmk_rc_ok; } // LCOV_EXCL_STOP // End deprecated API diff --git a/lib/common/output.c b/lib/common/output.c index 8cb273bf77..fe7ca9f5a6 100644 --- a/lib/common/output.c +++ b/lib/common/output.c @@ -1,252 +1,254 @@ /* * 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. */ #include #include #include #include static GHashTable *formatters = NULL; #if defined(PCMK__UNIT_TESTING) GHashTable * pcmk__output_formatters(void) { return formatters; } #endif void pcmk__output_free(pcmk__output_t *out) { if (out == NULL) { return; } out->free_priv(out); if (out->messages != NULL) { g_hash_table_destroy(out->messages); } g_free(out->request); free(out); } int pcmk__output_new(pcmk__output_t **out, const char *fmt_name, const char *filename, char **argv) { pcmk__output_factory_t create = NULL; CRM_ASSERT(formatters != NULL && out != NULL); /* If no name was given, just try "text". It's up to each tool to register * what it supports so this also may not be valid. */ if (fmt_name == NULL) { create = g_hash_table_lookup(formatters, "text"); } else { create = g_hash_table_lookup(formatters, fmt_name); } if (create == NULL) { return pcmk_rc_unknown_format; } *out = create(argv); if (*out == NULL) { return ENOMEM; } if (pcmk__str_eq(filename, "-", pcmk__str_null_matches)) { (*out)->dest = stdout; } else { (*out)->dest = fopen(filename, "w"); if ((*out)->dest == NULL) { pcmk__output_free(*out); *out = NULL; return errno; } } (*out)->quiet = false; (*out)->messages = pcmk__strkey_table(free, NULL); if ((*out)->init(*out) == false) { pcmk__output_free(*out); return ENOMEM; } setenv("OCF_OUTPUT_FORMAT", (*out)->fmt_name, 1); return pcmk_rc_ok; } int pcmk__register_format(GOptionGroup *group, const char *name, - pcmk__output_factory_t create, GOptionEntry *options) { + pcmk__output_factory_t create, + const GOptionEntry *options) +{ CRM_ASSERT(create != NULL && !pcmk__str_empty(name)); if (formatters == NULL) { formatters = pcmk__strkey_table(free, NULL); } if (options != NULL && group != NULL) { g_option_group_add_entries(group, options); } g_hash_table_insert(formatters, strdup(name), create); return pcmk_rc_ok; } void -pcmk__register_formats(GOptionGroup *group, pcmk__supported_format_t *formats) { - pcmk__supported_format_t *entry = NULL; - +pcmk__register_formats(GOptionGroup *group, + const pcmk__supported_format_t *formats) +{ if (formats == NULL) { return; } - - for (entry = formats; entry->name != NULL; entry++) { + for (const pcmk__supported_format_t *entry = formats; entry->name != NULL; + entry++) { pcmk__register_format(group, entry->name, entry->create, entry->options); } } void pcmk__unregister_formats() { if (formatters != NULL) { g_hash_table_destroy(formatters); formatters = NULL; } } int pcmk__call_message(pcmk__output_t *out, const char *message_id, ...) { va_list args; int rc = pcmk_rc_ok; pcmk__message_fn_t fn; CRM_ASSERT(out != NULL && !pcmk__str_empty(message_id)); fn = g_hash_table_lookup(out->messages, message_id); if (fn == NULL) { crm_debug("Called unknown output message '%s' for format '%s'", message_id, out->fmt_name); return EINVAL; } va_start(args, message_id); rc = fn(out, args); va_end(args); return rc; } void pcmk__register_message(pcmk__output_t *out, const char *message_id, pcmk__message_fn_t fn) { CRM_ASSERT(out != NULL && !pcmk__str_empty(message_id) && fn != NULL); g_hash_table_replace(out->messages, strdup(message_id), fn); } void -pcmk__register_messages(pcmk__output_t *out, pcmk__message_entry_t *table) { - pcmk__message_entry_t *entry; - - for (entry = table; entry->message_id != NULL; entry++) { +pcmk__register_messages(pcmk__output_t *out, const pcmk__message_entry_t *table) +{ + for (const pcmk__message_entry_t *entry = table; entry->message_id != NULL; + entry++) { if (pcmk__strcase_any_of(entry->fmt_name, "default", out->fmt_name, NULL)) { pcmk__register_message(out, entry->message_id, entry->fn); } } } void pcmk__output_and_clear_error(GError *error, pcmk__output_t *out) { if (error == NULL) { return; } if (out != NULL) { out->err(out, "%s: %s", g_get_prgname(), error->message); } else { fprintf(stderr, "%s: %s\n", g_get_prgname(), error->message); } g_clear_error(&error); } /*! * \internal * \brief Create an XML-only output object * * Create an output object that supports only the XML format, and free * existing XML if supplied (particularly useful for libpacemaker public API * functions that want to free any previous result supplied by the caller). * * \param[out] out Where to put newly created output object * \param[in,out] xml If non-NULL, this will be freed * * \return Standard Pacemaker return code */ int pcmk__xml_output_new(pcmk__output_t **out, xmlNodePtr *xml) { pcmk__supported_format_t xml_format[] = { PCMK__SUPPORTED_FORMAT_XML, { NULL, NULL, NULL } }; if (*xml != NULL) { xmlFreeNode(*xml); *xml = NULL; } pcmk__register_formats(NULL, xml_format); return pcmk__output_new(out, "xml", NULL, NULL); } /*! * \internal * \brief Finish and free an XML-only output object * * \param[in] out Output object to free * \param[out] xml Where to store XML output */ void pcmk__xml_output_finish(pcmk__output_t *out, xmlNodePtr *xml) { out->finish(out, 0, FALSE, (void **) xml); pcmk__output_free(out); } /*! * \internal * \brief Create a new output object using the "log" format * * \param[out] out Where to store newly allocated output object * * \return Standard Pacemaker return code */ int pcmk__log_output_new(pcmk__output_t **out) { int rc = pcmk_rc_ok; const char* argv[] = { "", NULL }; pcmk__supported_format_t formats[] = { PCMK__SUPPORTED_FORMAT_LOG, { NULL, NULL, NULL } }; pcmk__register_formats(NULL, formats); rc = pcmk__output_new(out, "log", NULL, (char **) argv); if ((rc != pcmk_rc_ok) || (*out == NULL)) { crm_err("Can't log certain messages due to internal error: %s", pcmk_rc_str(rc)); return rc; } return pcmk_rc_ok; } diff --git a/lib/common/xml.c b/lib/common/xml.c index a37f60e0eb..c254a953ec 100644 --- a/lib/common/xml.c +++ b/lib/common/xml.c @@ -1,3091 +1,3091 @@ /* * Copyright 2004-2022 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #include #include #include #include #include #include #include #include #include #include #include #include /* xmlAllocOutputBuffer */ #include #include #include #include // PCMK__XML_LOG_BASE, etc. #include "crmcommon_private.h" // Define this as 1 in development to get insanely verbose trace messages #ifndef XML_PARSER_DEBUG #define XML_PARSER_DEBUG 0 #endif /* @TODO XML_PARSE_RECOVER allows some XML errors to be silently worked around * by libxml2, which is potentially ambiguous and dangerous. We should drop it * when we can break backward compatibility with configurations that might be * relying on it (i.e. pacemaker 3.0.0). * * It might be a good idea to have a transitional period where we first try * parsing without XML_PARSE_RECOVER, and if that fails, try parsing again with * it, logging a warning if it succeeds. */ #define PCMK__XML_PARSE_OPTS (XML_PARSE_NOBLANKS | XML_PARSE_RECOVER) bool pcmk__tracking_xml_changes(xmlNode *xml, bool lazy) { if(xml == NULL || xml->doc == NULL || xml->doc->_private == NULL) { return FALSE; } else if (!pcmk_is_set(((xml_private_t *)xml->doc->_private)->flags, pcmk__xf_tracking)) { return FALSE; } else if (lazy && !pcmk_is_set(((xml_private_t *)xml->doc->_private)->flags, pcmk__xf_lazy)) { return FALSE; } return TRUE; } /*! * \internal * \brief Append spaces to a buffer * * Spaces are appended only if the \p xml_log_option_formatted flag is set. * * \param[in] options Group of \p xml_log_options flags * \param[in,out] buffer Where to append the spaces (must not be \p NULL) * \param[in] depth Current indentation level */ static inline void insert_prefix(int options, GString *buffer, int depth) { int spaces = 0; if (!pcmk_is_set(options, xml_log_option_formatted)) { return; } CRM_ASSERT(buffer != NULL); CRM_CHECK(depth >= 0, depth = 0); /* With -O2, this loop is faster than one g_string_append_printf() call with * width == 2 * depth */ spaces = 2 * depth; for (int lpc = 0; lpc < spaces; lpc++) { g_string_append_c(buffer, ' '); } } static inline void set_parent_flag(xmlNode *xml, long flag) { for(; xml; xml = xml->parent) { xml_private_t *p = xml->_private; if(p == NULL) { /* During calls to xmlDocCopyNode(), _private will be unset for parent nodes */ } else { pcmk__set_xml_flags(p, flag); } } } void pcmk__set_xml_doc_flag(xmlNode *xml, enum xml_private_flags flag) { if(xml && xml->doc && xml->doc->_private){ /* During calls to xmlDocCopyNode(), xml->doc may be unset */ xml_private_t *p = xml->doc->_private; pcmk__set_xml_flags(p, flag); } } // Mark document, element, and all element's parents as changed static inline void mark_xml_node_dirty(xmlNode *xml) { pcmk__set_xml_doc_flag(xml, pcmk__xf_dirty); set_parent_flag(xml, pcmk__xf_dirty); } // Clear flags on XML node and its children static void reset_xml_node_flags(xmlNode *xml) { xmlNode *cIter = NULL; xml_private_t *p = xml->_private; if (p) { p->flags = 0; } for (cIter = pcmk__xml_first_child(xml); cIter != NULL; cIter = pcmk__xml_next(cIter)) { reset_xml_node_flags(cIter); } } // Set xpf_created flag on XML node and any children void pcmk__mark_xml_created(xmlNode *xml) { xmlNode *cIter = NULL; xml_private_t *p = xml->_private; if (p && pcmk__tracking_xml_changes(xml, FALSE)) { if (!pcmk_is_set(p->flags, pcmk__xf_created)) { pcmk__set_xml_flags(p, pcmk__xf_created); mark_xml_node_dirty(xml); } for (cIter = pcmk__xml_first_child(xml); cIter != NULL; cIter = pcmk__xml_next(cIter)) { pcmk__mark_xml_created(cIter); } } } void pcmk__mark_xml_attr_dirty(xmlAttr *a) { xmlNode *parent = a->parent; xml_private_t *p = NULL; p = a->_private; pcmk__set_xml_flags(p, pcmk__xf_dirty|pcmk__xf_modified); pcmk__clear_xml_flags(p, pcmk__xf_deleted); mark_xml_node_dirty(parent); } #define XML_PRIVATE_MAGIC (long) 0x81726354 // Free an XML object previously marked as deleted static void free_deleted_object(void *data) { if(data) { pcmk__deleted_xml_t *deleted_obj = data; free(deleted_obj->path); free(deleted_obj); } } // Free and NULL user, ACLs, and deleted objects in an XML node's private data static void reset_xml_private_data(xml_private_t *p) { if(p) { CRM_ASSERT(p->check == XML_PRIVATE_MAGIC); free(p->user); p->user = NULL; if(p->acls) { pcmk__free_acls(p->acls); p->acls = NULL; } if(p->deleted_objs) { g_list_free_full(p->deleted_objs, free_deleted_object); p->deleted_objs = NULL; } } } // Free all private data associated with an XML node static void free_private_data(xmlNode *node) { /* need to explicitly avoid our custom _private field cleanup when called from internal XSLT cleanup (xsltApplyStylesheetInternal -> xsltFreeTransformContext -> xsltFreeRVTs -> xmlFreeDoc) onto result tree fragments, represented as standalone documents with otherwise infeasible space-prefixed name (xsltInternals.h: XSLT_MARK_RES_TREE_FRAG) and carrying it's own load at _private field -- later assert on the XML_PRIVATE_MAGIC would explode */ if (node->type != XML_DOCUMENT_NODE || node->name == NULL || node->name[0] != ' ') { reset_xml_private_data(node->_private); free(node->_private); } } // Allocate and initialize private data for an XML node static void new_private_data(xmlNode *node) { xml_private_t *p = NULL; switch(node->type) { case XML_ELEMENT_NODE: case XML_DOCUMENT_NODE: case XML_ATTRIBUTE_NODE: case XML_COMMENT_NODE: p = calloc(1, sizeof(xml_private_t)); p->check = XML_PRIVATE_MAGIC; /* Flags will be reset if necessary when tracking is enabled */ pcmk__set_xml_flags(p, pcmk__xf_dirty|pcmk__xf_created); node->_private = p; break; case XML_TEXT_NODE: case XML_DTD_NODE: case XML_CDATA_SECTION_NODE: break; default: /* Ignore */ crm_trace("Ignoring %p %d", node, node->type); CRM_LOG_ASSERT(node->type == XML_ELEMENT_NODE); break; } if(p && pcmk__tracking_xml_changes(node, FALSE)) { /* XML_ELEMENT_NODE doesn't get picked up here, node->doc is * not hooked up at the point we are called */ mark_xml_node_dirty(node); } } void xml_track_changes(xmlNode * xml, const char *user, xmlNode *acl_source, bool enforce_acls) { xml_accept_changes(xml); crm_trace("Tracking changes%s to %p", enforce_acls?" with ACLs":"", xml); pcmk__set_xml_doc_flag(xml, pcmk__xf_tracking); if(enforce_acls) { if(acl_source == NULL) { acl_source = xml; } pcmk__set_xml_doc_flag(xml, pcmk__xf_acl_enabled); pcmk__unpack_acl(acl_source, xml, user); pcmk__apply_acl(xml); } } bool xml_tracking_changes(xmlNode * xml) { return (xml != NULL) && (xml->doc != NULL) && (xml->doc->_private != NULL) && pcmk_is_set(((xml_private_t *)(xml->doc->_private))->flags, pcmk__xf_tracking); } bool xml_document_dirty(xmlNode *xml) { return (xml != NULL) && (xml->doc != NULL) && (xml->doc->_private != NULL) && pcmk_is_set(((xml_private_t *)(xml->doc->_private))->flags, pcmk__xf_dirty); } /*! * \internal * \brief Return ordinal position of an XML node among its siblings * * \param[in] xml XML node to check * \param[in] ignore_if_set Don't count siblings with this flag set * * \return Ordinal position of \p xml (starting with 0) */ int pcmk__xml_position(xmlNode *xml, enum xml_private_flags ignore_if_set) { int position = 0; xmlNode *cIter = NULL; for(cIter = xml; cIter->prev; cIter = cIter->prev) { xml_private_t *p = ((xmlNode*)cIter->prev)->_private; if (!pcmk_is_set(p->flags, ignore_if_set)) { position++; } } return position; } // This also clears attribute's flags if not marked as deleted static bool marked_as_deleted(xmlAttrPtr a, void *user_data) { xml_private_t *p = a->_private; if (pcmk_is_set(p->flags, pcmk__xf_deleted)) { return true; } p->flags = pcmk__xf_none; return false; } // Remove all attributes marked as deleted from an XML node static void accept_attr_deletions(xmlNode *xml) { // Clear XML node's flags ((xml_private_t *) xml->_private)->flags = pcmk__xf_none; // Remove this XML node's attributes that were marked as deleted pcmk__xe_remove_matching_attrs(xml, marked_as_deleted, NULL); // Recursively do the same for this XML node's children for (xmlNodePtr cIter = pcmk__xml_first_child(xml); cIter != NULL; cIter = pcmk__xml_next(cIter)) { accept_attr_deletions(cIter); } } /*! * \internal * \brief Find first child XML node matching another given XML node * * \param[in] haystack XML whose children should be checked * \param[in] needle XML to match (comment content or element name and ID) * \param[in] exact If true and needle is a comment, position must match */ xmlNode * pcmk__xml_match(xmlNode *haystack, xmlNode *needle, bool exact) { CRM_CHECK(needle != NULL, return NULL); if (needle->type == XML_COMMENT_NODE) { return pcmk__xc_match(haystack, needle, exact); } else { const char *id = ID(needle); const char *attr = (id == NULL)? NULL : XML_ATTR_ID; return pcmk__xe_match(haystack, crm_element_name(needle), attr, id); } } void xml_log_changes(uint8_t log_level, const char *function, xmlNode * xml) { GList *gIter = NULL; xml_private_t *doc = NULL; if (log_level == LOG_NEVER) { return; } CRM_ASSERT(xml); CRM_ASSERT(xml->doc); doc = xml->doc->_private; if (!pcmk_is_set(doc->flags, pcmk__xf_dirty)) { return; } for(gIter = doc->deleted_objs; gIter; gIter = gIter->next) { pcmk__deleted_xml_t *deleted_obj = gIter->data; if (deleted_obj->position >= 0) { do_crm_log_alias(log_level, __FILE__, function, __LINE__, "-- %s (%d)", deleted_obj->path, deleted_obj->position); } else { do_crm_log_alias(log_level, __FILE__, function, __LINE__, "-- %s", deleted_obj->path); } } log_data_element(log_level, __FILE__, function, __LINE__, "+ ", xml, 0, xml_log_option_formatted|xml_log_option_dirty_add); } void xml_accept_changes(xmlNode * xml) { xmlNode *top = NULL; xml_private_t *doc = NULL; if(xml == NULL) { return; } crm_trace("Accepting changes to %p", xml); doc = xml->doc->_private; top = xmlDocGetRootElement(xml->doc); reset_xml_private_data(xml->doc->_private); if (!pcmk_is_set(doc->flags, pcmk__xf_dirty)) { doc->flags = pcmk__xf_none; return; } doc->flags = pcmk__xf_none; accept_attr_deletions(top); } xmlNode * find_xml_node(const xmlNode *root, const char *search_path, gboolean must_find) { xmlNode *a_child = NULL; const char *name = "NULL"; if (root != NULL) { name = crm_element_name(root); } if (search_path == NULL) { crm_warn("Will never find "); return NULL; } for (a_child = pcmk__xml_first_child(root); a_child != NULL; a_child = pcmk__xml_next(a_child)) { if (strcmp((const char *)a_child->name, search_path) == 0) { /* crm_trace("returning node (%s).", crm_element_name(a_child)); */ return a_child; } } if (must_find) { crm_warn("Could not find %s in %s.", search_path, name); } else if (root != NULL) { crm_trace("Could not find %s in %s.", search_path, name); } else { crm_trace("Could not find %s in .", search_path); } return NULL; } #define attr_matches(c, n, v) pcmk__str_eq(crm_element_value((c), (n)), \ (v), pcmk__str_none) /*! * \internal * \brief Find first XML child element matching given criteria * * \param[in] parent XML element to search * \param[in] node_name If not NULL, only match children of this type * \param[in] attr_n If not NULL, only match children with an attribute * of this name and a value of \p attr_v * \param[in] attr_v If \p attr_n and this are not NULL, only match children * with an attribute named \p attr_n and this value * * \return Matching XML child element, or NULL if none found */ xmlNode * pcmk__xe_match(xmlNode *parent, const char *node_name, const char *attr_n, const char *attr_v) { /* ensure attr_v specified when attr_n is */ CRM_CHECK(attr_n == NULL || attr_v != NULL, return NULL); for (xmlNode *child = pcmk__xml_first_child(parent); child != NULL; child = pcmk__xml_next(child)) { if (pcmk__str_eq(node_name, (const char *) (child->name), pcmk__str_null_matches) && ((attr_n == NULL) || attr_matches(child, attr_n, attr_v))) { return child; } } crm_trace("XML child node <%s%s%s%s%s> not found in %s", (node_name? node_name : "(any)"), (attr_n? " " : ""), (attr_n? attr_n : ""), (attr_n? "=" : ""), (attr_n? attr_v : ""), crm_element_name(parent)); return NULL; } void copy_in_properties(xmlNode * target, xmlNode * src) { if (src == NULL) { crm_warn("No node to copy properties from"); } else if (target == NULL) { crm_err("No node to copy properties into"); } else { for (xmlAttrPtr a = pcmk__xe_first_attr(src); a != NULL; a = a->next) { const char *p_name = (const char *) a->name; const char *p_value = pcmk__xml_attr_value(a); expand_plus_plus(target, p_name, p_value); if (xml_acl_denied(target)) { crm_trace("Cannot copy %s=%s to %s", p_name, p_value, target->name); return; } } } return; } /*! * \brief Parse integer assignment statements on this node and all its child * nodes * * \param[in] target root XML node to be processed * * \note This function is recursive */ void fix_plus_plus_recursive(xmlNode * target) { /* TODO: Remove recursion and use xpath searches for value++ */ xmlNode *child = NULL; for (xmlAttrPtr a = pcmk__xe_first_attr(target); a != NULL; a = a->next) { const char *p_name = (const char *) a->name; const char *p_value = pcmk__xml_attr_value(a); expand_plus_plus(target, p_name, p_value); } for (child = pcmk__xml_first_child(target); child != NULL; child = pcmk__xml_next(child)) { fix_plus_plus_recursive(child); } } /*! * \brief Update current XML attribute value per parsed integer assignment statement * * \param[in,out] target an XML node, containing a XML attribute that is * initialized to some numeric value, to be processed * \param[in] name name of the XML attribute, e.g. X, whose value * should be updated * \param[in] value assignment statement, e.g. "X++" or * "X+=5", to be applied to the initialized value. * * \note The original XML attribute value is treated as 0 if non-numeric and * truncated to be an integer if decimal-point-containing. * \note The final XML attribute value is truncated to not exceed 1000000. * \note Undefined behavior if unexpected input. */ void expand_plus_plus(xmlNode * target, const char *name, const char *value) { int offset = 1; int name_len = 0; int int_value = 0; int value_len = 0; const char *old_value = NULL; if (target == NULL || value == NULL || name == NULL) { return; } old_value = crm_element_value(target, name); if (old_value == NULL) { /* if no previous value, set unexpanded */ goto set_unexpanded; } else if (strstr(value, name) != value) { goto set_unexpanded; } name_len = strlen(name); value_len = strlen(value); if (value_len < (name_len + 2) || value[name_len] != '+' || (value[name_len + 1] != '+' && value[name_len + 1] != '=')) { goto set_unexpanded; } /* if we are expanding ourselves, * then no previous value was set and leave int_value as 0 */ if (old_value != value) { int_value = char2score(old_value); } if (value[name_len + 1] != '+') { const char *offset_s = value + (name_len + 2); offset = char2score(offset_s); } int_value += offset; if (int_value > INFINITY) { int_value = (int)INFINITY; } crm_xml_add_int(target, name, int_value); return; set_unexpanded: if (old_value == value) { /* the old value is already set, nothing to do */ return; } crm_xml_add(target, name, value); return; } /*! * \internal * \brief Remove an XML element's attributes that match some criteria * * \param[in,out] element XML element to modify * \param[in] match If not NULL, only remove attributes for which * this function returns true * \param[in] user_data Data to pass to \p match */ void pcmk__xe_remove_matching_attrs(xmlNode *element, bool (*match)(xmlAttrPtr, void *), void *user_data) { xmlAttrPtr next = NULL; for (xmlAttrPtr a = pcmk__xe_first_attr(element); a != NULL; a = next) { next = a->next; // Grab now because attribute might get removed if ((match == NULL) || match(a, user_data)) { if (!pcmk__check_acl(element, NULL, pcmk__xf_acl_write)) { crm_trace("ACLs prevent removal of attributes (%s and " "possibly others) from %s element", (const char *) a->name, (const char *) element->name); return; // ACLs apply to element, not particular attributes } if (pcmk__tracking_xml_changes(element, false)) { // Leave (marked for removal) until after diff is calculated set_parent_flag(element, pcmk__xf_dirty); pcmk__set_xml_flags((xml_private_t *) a->_private, pcmk__xf_deleted); } else { xmlRemoveProp(a); } } } } xmlDoc * getDocPtr(xmlNode * node) { xmlDoc *doc = NULL; CRM_CHECK(node != NULL, return NULL); doc = node->doc; if (doc == NULL) { doc = xmlNewDoc((pcmkXmlStr) "1.0"); xmlDocSetRootElement(doc, node); xmlSetTreeDoc(node, doc); } return doc; } xmlNode * add_node_copy(xmlNode * parent, xmlNode * src_node) { xmlNode *child = NULL; xmlDoc *doc = getDocPtr(parent); CRM_CHECK(src_node != NULL, return NULL); child = xmlDocCopyNode(src_node, doc, 1); xmlAddChild(parent, child); pcmk__mark_xml_created(child); return child; } int add_node_nocopy(xmlNode * parent, const char *name, xmlNode * child) { add_node_copy(parent, child); free_xml(child); return 1; } xmlNode * create_xml_node(xmlNode * parent, const char *name) { xmlDoc *doc = NULL; xmlNode *node = NULL; if (pcmk__str_empty(name)) { CRM_CHECK(name != NULL && name[0] == 0, return NULL); return NULL; } if (parent == NULL) { doc = xmlNewDoc((pcmkXmlStr) "1.0"); node = xmlNewDocRawNode(doc, NULL, (pcmkXmlStr) name, NULL); xmlDocSetRootElement(doc, node); } else { doc = getDocPtr(parent); node = xmlNewDocRawNode(doc, NULL, (pcmkXmlStr) name, NULL); xmlAddChild(parent, node); } pcmk__mark_xml_created(node); return node; } xmlNode * pcmk_create_xml_text_node(xmlNode * parent, const char *name, const char *content) { xmlNode *node = create_xml_node(parent, name); if (node != NULL) { xmlNodeSetContent(node, (pcmkXmlStr) content); } return node; } xmlNode * pcmk_create_html_node(xmlNode * parent, const char *element_name, const char *id, const char *class_name, const char *text) { xmlNode *node = pcmk_create_xml_text_node(parent, element_name, text); if (class_name != NULL) { crm_xml_add(node, "class", class_name); } if (id != NULL) { crm_xml_add(node, "id", id); } return node; } /*! * Free an XML element and all of its children, removing it from its parent * * \param[in] xml XML element to free */ void pcmk_free_xml_subtree(xmlNode *xml) { xmlUnlinkNode(xml); // Detaches from parent and siblings xmlFreeNode(xml); // Frees } static void free_xml_with_position(xmlNode * child, int position) { if (child != NULL) { xmlNode *top = NULL; xmlDoc *doc = child->doc; xml_private_t *p = child->_private; if (doc != NULL) { top = xmlDocGetRootElement(doc); } if (doc != NULL && top == child) { /* Free everything */ xmlFreeDoc(doc); } else if (pcmk__check_acl(child, NULL, pcmk__xf_acl_write) == FALSE) { GString *xpath = NULL; pcmk__log_else(LOG_TRACE, return); xpath = pcmk__element_xpath(child); qb_log_from_external_source(__func__, __FILE__, "Cannot remove %s %x", LOG_TRACE, __LINE__, 0, (const char *) xpath->str, p->flags); g_string_free(xpath, TRUE); return; } else { if (doc && pcmk__tracking_xml_changes(child, FALSE) && !pcmk_is_set(p->flags, pcmk__xf_created)) { GString *xpath = pcmk__element_xpath(child); if (xpath != NULL) { pcmk__deleted_xml_t *deleted_obj = NULL; crm_trace("Deleting %s %p from %p", (const char *) xpath->str, child, doc); deleted_obj = calloc(1, sizeof(pcmk__deleted_xml_t)); deleted_obj->path = strdup((const char *) xpath->str); CRM_ASSERT(deleted_obj->path != NULL); g_string_free(xpath, TRUE); deleted_obj->position = -1; /* Record the "position" only for XML comments for now */ if (child->type == XML_COMMENT_NODE) { if (position >= 0) { deleted_obj->position = position; } else { deleted_obj->position = pcmk__xml_position(child, pcmk__xf_skip); } } p = doc->_private; p->deleted_objs = g_list_append(p->deleted_objs, deleted_obj); pcmk__set_xml_doc_flag(child, pcmk__xf_dirty); } } pcmk_free_xml_subtree(child); } } } void free_xml(xmlNode * child) { free_xml_with_position(child, -1); } xmlNode * copy_xml(xmlNode * src) { xmlDoc *doc = xmlNewDoc((pcmkXmlStr) "1.0"); xmlNode *copy = xmlDocCopyNode(src, doc, 1); xmlDocSetRootElement(doc, copy); xmlSetTreeDoc(copy, doc); return copy; } static void log_xmllib_err(void *ctx, const char *fmt, ...) G_GNUC_PRINTF(2, 3); // Log an XML library error static void log_xmllib_err(void *ctx, const char *fmt, ...) { va_list ap; static struct qb_log_callsite *xml_error_cs = NULL; if (xml_error_cs == NULL) { xml_error_cs = qb_log_callsite_get( __func__, __FILE__, "xml library error", LOG_TRACE, __LINE__, crm_trace_nonlog); } va_start(ap, fmt); if (xml_error_cs && xml_error_cs->targets) { PCMK__XML_LOG_BASE(LOG_ERR, TRUE, crm_abort(__FILE__, __PRETTY_FUNCTION__, __LINE__, "xml library error", TRUE, TRUE), "XML Error: ", fmt, ap); } else { PCMK__XML_LOG_BASE(LOG_ERR, TRUE, 0, "XML Error: ", fmt, ap); } va_end(ap); } xmlNode * string2xml(const char *input) { xmlNode *xml = NULL; xmlDocPtr output = NULL; xmlParserCtxtPtr ctxt = NULL; xmlErrorPtr last_error = NULL; if (input == NULL) { crm_err("Can't parse NULL input"); return NULL; } /* create a parser context */ ctxt = xmlNewParserCtxt(); CRM_CHECK(ctxt != NULL, return NULL); xmlCtxtResetLastError(ctxt); xmlSetGenericErrorFunc(ctxt, log_xmllib_err); output = xmlCtxtReadDoc(ctxt, (pcmkXmlStr) input, NULL, NULL, PCMK__XML_PARSE_OPTS); if (output) { xml = xmlDocGetRootElement(output); } last_error = xmlCtxtGetLastError(ctxt); if (last_error && last_error->code != XML_ERR_OK) { /* crm_abort(__FILE__,__func__,__LINE__, "last_error->code != XML_ERR_OK", TRUE, TRUE); */ /* * http://xmlsoft.org/html/libxml-xmlerror.html#xmlErrorLevel * http://xmlsoft.org/html/libxml-xmlerror.html#xmlParserErrors */ crm_warn("Parsing failed (domain=%d, level=%d, code=%d): %s", last_error->domain, last_error->level, last_error->code, last_error->message); if (last_error->code == XML_ERR_DOCUMENT_EMPTY) { CRM_LOG_ASSERT("Cannot parse an empty string"); } else if (last_error->code != XML_ERR_DOCUMENT_END) { crm_err("Couldn't%s parse %d chars: %s", xml ? " fully" : "", (int)strlen(input), input); if (xml != NULL) { crm_log_xml_err(xml, "Partial"); } } else { int len = strlen(input); int lpc = 0; while(lpc < len) { crm_warn("Parse error[+%.3d]: %.80s", lpc, input+lpc); lpc += 80; } CRM_LOG_ASSERT("String parsing error"); } } xmlFreeParserCtxt(ctxt); return xml; } xmlNode * stdin2xml(void) { size_t data_length = 0; size_t read_chars = 0; char *xml_buffer = NULL; xmlNode *xml_obj = NULL; do { xml_buffer = pcmk__realloc(xml_buffer, data_length + PCMK__BUFFER_SIZE); read_chars = fread(xml_buffer + data_length, 1, PCMK__BUFFER_SIZE, stdin); data_length += read_chars; } while (read_chars == PCMK__BUFFER_SIZE); if (data_length == 0) { crm_warn("No XML supplied on stdin"); free(xml_buffer); return NULL; } xml_buffer[data_length] = '\0'; xml_obj = string2xml(xml_buffer); free(xml_buffer); crm_log_xml_trace(xml_obj, "Created fragment"); return xml_obj; } static char * decompress_file(const char *filename) { char *buffer = NULL; int rc = 0; size_t length = 0, read_len = 0; BZFILE *bz_file = NULL; FILE *input = fopen(filename, "r"); if (input == NULL) { crm_perror(LOG_ERR, "Could not open %s for reading", filename); return NULL; } bz_file = BZ2_bzReadOpen(&rc, input, 0, 0, NULL, 0); if (rc != BZ_OK) { crm_err("Could not prepare to read compressed %s: %s " CRM_XS " bzerror=%d", filename, bz2_strerror(rc), rc); BZ2_bzReadClose(&rc, bz_file); fclose(input); return NULL; } rc = BZ_OK; // cppcheck seems not to understand the abort-logic in pcmk__realloc // cppcheck-suppress memleak while (rc == BZ_OK) { buffer = pcmk__realloc(buffer, PCMK__BUFFER_SIZE + length + 1); read_len = BZ2_bzRead(&rc, bz_file, buffer + length, PCMK__BUFFER_SIZE); crm_trace("Read %ld bytes from file: %d", (long)read_len, rc); if (rc == BZ_OK || rc == BZ_STREAM_END) { length += read_len; } } buffer[length] = '\0'; if (rc != BZ_STREAM_END) { crm_err("Could not read compressed %s: %s " CRM_XS " bzerror=%d", filename, bz2_strerror(rc), rc); free(buffer); buffer = NULL; } BZ2_bzReadClose(&rc, bz_file); fclose(input); return buffer; } /*! * \internal * \brief Remove XML text nodes from specified XML and all its children * * \param[in,out] xml XML to strip text from */ void pcmk__strip_xml_text(xmlNode *xml) { xmlNode *iter = xml->children; while (iter) { xmlNode *next = iter->next; switch (iter->type) { case XML_TEXT_NODE: /* Remove it */ pcmk_free_xml_subtree(iter); break; case XML_ELEMENT_NODE: /* Search it */ pcmk__strip_xml_text(iter); break; default: /* Leave it */ break; } iter = next; } } xmlNode * filename2xml(const char *filename) { xmlNode *xml = NULL; xmlDocPtr output = NULL; bool uncompressed = true; xmlParserCtxtPtr ctxt = NULL; xmlErrorPtr last_error = NULL; /* create a parser context */ ctxt = xmlNewParserCtxt(); CRM_CHECK(ctxt != NULL, return NULL); xmlCtxtResetLastError(ctxt); xmlSetGenericErrorFunc(ctxt, log_xmllib_err); if (filename) { uncompressed = !pcmk__ends_with_ext(filename, ".bz2"); } if (pcmk__str_eq(filename, "-", pcmk__str_null_matches)) { /* STDIN_FILENO == fileno(stdin) */ output = xmlCtxtReadFd(ctxt, STDIN_FILENO, "unknown.xml", NULL, PCMK__XML_PARSE_OPTS); } else if (uncompressed) { output = xmlCtxtReadFile(ctxt, filename, NULL, PCMK__XML_PARSE_OPTS); } else { char *input = decompress_file(filename); output = xmlCtxtReadDoc(ctxt, (pcmkXmlStr) input, NULL, NULL, PCMK__XML_PARSE_OPTS); free(input); } if (output && (xml = xmlDocGetRootElement(output))) { pcmk__strip_xml_text(xml); } last_error = xmlCtxtGetLastError(ctxt); if (last_error && last_error->code != XML_ERR_OK) { /* crm_abort(__FILE__,__func__,__LINE__, "last_error->code != XML_ERR_OK", TRUE, TRUE); */ /* * http://xmlsoft.org/html/libxml-xmlerror.html#xmlErrorLevel * http://xmlsoft.org/html/libxml-xmlerror.html#xmlParserErrors */ crm_err("Parsing failed (domain=%d, level=%d, code=%d): %s", last_error->domain, last_error->level, last_error->code, last_error->message); if (last_error && last_error->code != XML_ERR_OK) { crm_err("Couldn't%s parse %s", xml ? " fully" : "", filename); if (xml != NULL) { crm_log_xml_err(xml, "Partial"); } } } xmlFreeParserCtxt(ctxt); return xml; } /*! * \internal * \brief Add a "last written" attribute to an XML element, set to current time * * \param[in] xe XML element to add attribute to * * \return Value that was set, or NULL on error */ const char * pcmk__xe_add_last_written(xmlNode *xe) { const char *now_str = pcmk__epoch2str(NULL); return crm_xml_add(xe, XML_CIB_ATTR_WRITTEN, now_str ? now_str : "Could not determine current time"); } /*! * \brief Sanitize a string so it is usable as an XML ID * * \param[in,out] id String to sanitize */ void crm_xml_sanitize_id(char *id) { char *c; for (c = id; *c; ++c) { /* @TODO Sanitize more comprehensively */ switch (*c) { case ':': case '#': *c = '.'; } } } /*! * \brief Set the ID of an XML element using a format * * \param[in,out] xml XML element * \param[in] fmt printf-style format * \param[in] ... any arguments required by format */ void crm_xml_set_id(xmlNode *xml, const char *format, ...) { va_list ap; int len = 0; char *id = NULL; /* equivalent to crm_strdup_printf() */ va_start(ap, format); len = vasprintf(&id, format, ap); va_end(ap); CRM_ASSERT(len > 0); crm_xml_sanitize_id(id); crm_xml_add(xml, XML_ATTR_ID, id); free(id); } /*! * \internal * \brief Write XML to a file stream * * \param[in] xml_node XML to write * \param[in] filename Name of file being written (for logging only) * \param[in] stream Open file stream corresponding to filename * \param[in] compress Whether to compress XML before writing * \param[out] nbytes Number of bytes written * * \return Standard Pacemaker return code */ static int write_xml_stream(xmlNode *xml_node, const char *filename, FILE *stream, bool compress, unsigned int *nbytes) { int rc = pcmk_rc_ok; char *buffer = NULL; *nbytes = 0; crm_log_xml_trace(xml_node, "writing"); buffer = dump_xml_formatted(xml_node); CRM_CHECK(buffer && strlen(buffer), crm_log_xml_warn(xml_node, "formatting failed"); rc = pcmk_rc_error; goto bail); if (compress) { unsigned int in = 0; BZFILE *bz_file = NULL; rc = BZ_OK; bz_file = BZ2_bzWriteOpen(&rc, stream, 5, 0, 30); if (rc != BZ_OK) { crm_warn("Not compressing %s: could not prepare file stream: %s " CRM_XS " bzerror=%d", filename, bz2_strerror(rc), rc); } else { BZ2_bzWrite(&rc, bz_file, buffer, strlen(buffer)); if (rc != BZ_OK) { crm_warn("Not compressing %s: could not compress data: %s " CRM_XS " bzerror=%d errno=%d", filename, bz2_strerror(rc), rc, errno); } } if (rc == BZ_OK) { BZ2_bzWriteClose(&rc, bz_file, 0, &in, nbytes); if (rc != BZ_OK) { crm_warn("Not compressing %s: could not write compressed data: %s " CRM_XS " bzerror=%d errno=%d", filename, bz2_strerror(rc), rc, errno); *nbytes = 0; // retry without compression } else { crm_trace("Compressed XML for %s from %u bytes to %u", filename, in, *nbytes); } } rc = pcmk_rc_ok; // Either true, or we'll retry without compression } if (*nbytes == 0) { rc = fprintf(stream, "%s", buffer); if (rc < 0) { rc = errno; crm_perror(LOG_ERR, "writing %s", filename); } else { *nbytes = (unsigned int) rc; rc = pcmk_rc_ok; } } bail: if (fflush(stream) != 0) { rc = errno; crm_perror(LOG_ERR, "flushing %s", filename); } /* Don't report error if the file does not support synchronization */ if (fsync(fileno(stream)) < 0 && errno != EROFS && errno != EINVAL) { rc = errno; crm_perror(LOG_ERR, "synchronizing %s", filename); } fclose(stream); crm_trace("Saved %d bytes to %s as XML", *nbytes, filename); free(buffer); return rc; } /*! * \brief Write XML to a file descriptor * * \param[in] xml_node XML to write * \param[in] filename Name of file being written (for logging only) * \param[in] fd Open file descriptor corresponding to filename * \param[in] compress Whether to compress XML before writing * * \return Number of bytes written on success, -errno otherwise */ int write_xml_fd(xmlNode * xml_node, const char *filename, int fd, gboolean compress) { FILE *stream = NULL; unsigned int nbytes = 0; int rc = pcmk_rc_ok; CRM_CHECK(xml_node && (fd > 0), return -EINVAL); stream = fdopen(fd, "w"); if (stream == NULL) { return -errno; } rc = write_xml_stream(xml_node, filename, stream, compress, &nbytes); if (rc != pcmk_rc_ok) { return pcmk_rc2legacy(rc); } return (int) nbytes; } /*! * \brief Write XML to a file * * \param[in] xml_node XML to write * \param[in] filename Name of file to write * \param[in] compress Whether to compress XML before writing * * \return Number of bytes written on success, -errno otherwise */ int write_xml_file(xmlNode * xml_node, const char *filename, gboolean compress) { FILE *stream = NULL; unsigned int nbytes = 0; int rc = pcmk_rc_ok; CRM_CHECK(xml_node && filename, return -EINVAL); stream = fopen(filename, "w"); if (stream == NULL) { return -errno; } rc = write_xml_stream(xml_node, filename, stream, compress, &nbytes); if (rc != pcmk_rc_ok) { return pcmk_rc2legacy(rc); } return (int) nbytes; } // Replace a portion of a dynamically allocated string (reallocating memory) static char * replace_text(char *text, int start, size_t *length, const char *replace) { size_t offset = strlen(replace) - 1; // We have space for 1 char already *length += offset; text = pcmk__realloc(text, *length); for (size_t lpc = (*length) - 1; lpc > (start + offset); lpc--) { text[lpc] = text[lpc - offset]; } memcpy(text + start, replace, offset + 1); return text; } /*! * \brief Replace special characters with their XML escape sequences * * \param[in] text Text to escape * * \return Newly allocated string equivalent to \p text but with special * characters replaced with XML escape sequences (or NULL if \p text * is NULL) */ char * crm_xml_escape(const char *text) { size_t length; char *copy; /* * When xmlCtxtReadDoc() parses < and friends in a * value, it converts them to their human readable * form. * * If one uses xmlNodeDump() to convert it back to a * string, all is well, because special characters are * converted back to their escape sequences. * * However xmlNodeDump() is randomly dog slow, even with the same * input. So we need to replicate the escaping in our custom * version so that the result can be re-parsed by xmlCtxtReadDoc() * when necessary. */ if (text == NULL) { return NULL; } length = 1 + strlen(text); copy = strdup(text); CRM_ASSERT(copy != NULL); for (size_t index = 0; index < length; index++) { if(copy[index] & 0x80 && copy[index+1] & 0x80){ index++; break; } switch (copy[index]) { case 0: break; case '<': copy = replace_text(copy, index, &length, "<"); break; case '>': copy = replace_text(copy, index, &length, ">"); break; case '"': copy = replace_text(copy, index, &length, """); break; case '\'': copy = replace_text(copy, index, &length, "'"); break; case '&': copy = replace_text(copy, index, &length, "&"); break; case '\t': /* Might as well just expand to a few spaces... */ copy = replace_text(copy, index, &length, " "); break; case '\n': copy = replace_text(copy, index, &length, "\\n"); break; case '\r': copy = replace_text(copy, index, &length, "\\r"); break; default: /* Check for and replace non-printing characters with their octal equivalent */ if(copy[index] < ' ' || copy[index] > '~') { char *replace = crm_strdup_printf("\\%.3o", copy[index]); copy = replace_text(copy, index, &length, replace); free(replace); } } } return copy; } /* Keep this inline. De-inlining resulted in a 0.6% average slowdown in * crm_simulate on cts/scheduler/xml during testing. */ /*! * \internal * \brief Append an XML attribute to a buffer * * \param[in] attr Attribute to append * \param[in] options Group of \p xml_log_options flags * \param[in,out] buffer Where to append the content (must not be \p NULL) */ static inline void dump_xml_attr(const xmlAttr *attr, int options, GString *buffer) { char *p_value = NULL; const char *p_name = NULL; xml_private_t *p = NULL; CRM_ASSERT(buffer != NULL); if (attr == NULL || attr->children == NULL) { return; } p = attr->_private; if (p && pcmk_is_set(p->flags, pcmk__xf_deleted)) { return; } p_name = (const char *) attr->name; p_value = crm_xml_escape((const char *)attr->children->content); g_string_append_printf(buffer, " %s=\"%s\"", p_name, pcmk__s(p_value, "")); free(p_value); } // Log an XML element or comment (and any children) in a formatted way static void log_xml_node_and_children(GString *buffer, int log_level, const char *file, const char *function, int line, const char *prefix, const xmlNode *data, int depth, int options) { const char *name = NULL; const char *hidden = NULL; xmlNode *child = NULL; CRM_ASSERT(buffer != NULL); if ((data == NULL) || (log_level == LOG_NEVER) || ((data->type != XML_COMMENT_NODE) && (data->type != XML_ELEMENT_NODE))) { return; } name = crm_element_name(data); g_string_truncate(buffer, 0); if (pcmk_is_set(options, xml_log_option_open)) { insert_prefix(options, buffer, depth); if (data->type == XML_COMMENT_NODE) { g_string_append_printf(buffer, "", (const gchar *) data->content); } else { g_string_append_printf(buffer, "<%s", name); hidden = crm_element_value(data, "hidden"); for (xmlAttrPtr a = pcmk__xe_first_attr(data); a != NULL; a = a->next) { xml_private_t *p = a->_private; const char *p_name = (const char *) a->name; const char *p_value = pcmk__xml_attr_value(a); char *p_copy = NULL; if (pcmk_is_set(p->flags, pcmk__xf_deleted)) { continue; } else if (pcmk_any_flags_set(options, xml_log_option_diff_plus |xml_log_option_diff_minus) && (strcmp(XML_DIFF_MARKER, p_name) == 0)) { continue; } else if (hidden != NULL && p_name[0] != 0 && strstr(hidden, p_name) != NULL) { p_copy = strdup("*****"); } else { p_copy = crm_xml_escape(p_value); } g_string_append_printf(buffer, " %s=\"%s\"", p_name, pcmk__s(p_copy, "")); free(p_copy); } if(xml_has_children(data) == FALSE) { g_string_append(buffer, "/>"); } else if (pcmk_is_set(options, xml_log_option_children)) { g_string_append_c(buffer, '>'); } else { g_string_append(buffer, "/>"); } } do_crm_log_alias(log_level, file, function, line, "%s %s", prefix, (const char *) buffer->str); } if(data->type == XML_COMMENT_NODE) { return; } else if(xml_has_children(data) == FALSE) { return; } else if (pcmk_is_set(options, xml_log_option_children)) { for (child = pcmk__xml_first_child(data); child != NULL; child = pcmk__xml_next(child)) { log_xml_node_and_children(buffer, log_level, file, function, line, prefix, child, depth + 1, options|xml_log_option_open |xml_log_option_close); } } if (pcmk_is_set(options, xml_log_option_close)) { g_string_truncate(buffer, 0); insert_prefix(options, buffer, depth); g_string_append_printf(buffer, "", name); do_crm_log_alias(log_level, file, function, line, "%s %s", prefix, (const char *) buffer->str); } } // Log an XML element or comment (and any children) in a formatted way void pcmk__xml_log(int log_level, const char *file, const char *function, int line, const char *prefix, const xmlNode *data, int depth, int options) { /* Allocate a buffer once, for log_xml_node_and_children() to truncate and * reuse in recursive calls */ GString *buffer = g_string_sized_new(1024); log_xml_node_and_children(buffer, log_level, file, function, line, prefix, data, depth, options); g_string_free(buffer, TRUE); } // Log XML portions that have been marked as changed static void log_xml_changes(int log_level, const char *file, const char *function, int line, const char *prefix, const xmlNode *data, int depth, int options) { xml_private_t *p; char *prefix_m = NULL; xmlNode *child = NULL; if ((data == NULL) || (log_level == LOG_NEVER)) { return; } p = data->_private; prefix_m = strdup(prefix); prefix_m[1] = '+'; if (pcmk_all_flags_set(p->flags, pcmk__xf_dirty|pcmk__xf_created)) { /* Continue and log full subtree */ pcmk__xml_log(log_level, file, function, line, prefix_m, data, depth, options|xml_log_option_open|xml_log_option_close |xml_log_option_children); } else if (pcmk_is_set(p->flags, pcmk__xf_dirty)) { int spaces = 0; char *prefix_del = NULL; char *prefix_moved = NULL; const char *flags = prefix; if (pcmk_is_set(options, xml_log_option_formatted)) { CRM_CHECK(depth >= 0, depth = 0); spaces = 2 * depth; } prefix_del = strdup(prefix); prefix_del[0] = '-'; prefix_del[1] = '-'; prefix_moved = strdup(prefix); prefix_moved[1] = '~'; if (pcmk_is_set(p->flags, pcmk__xf_moved)) { flags = prefix_moved; } else { flags = prefix; } pcmk__xml_log(log_level, file, function, line, flags, data, depth, options|xml_log_option_open); for (xmlAttrPtr a = pcmk__xe_first_attr(data); a != NULL; a = a->next) { const char *aname = (const char*) a->name; p = a->_private; if (pcmk_is_set(p->flags, pcmk__xf_deleted)) { const char *value = crm_element_value(data, aname); flags = prefix_del; do_crm_log_alias(log_level, file, function, line, "%s %*s @%s=%s", flags, spaces, "", aname, value); } else if (pcmk_is_set(p->flags, pcmk__xf_dirty)) { const char *value = crm_element_value(data, aname); if (pcmk_is_set(p->flags, pcmk__xf_created)) { flags = prefix_m; } else if (pcmk_is_set(p->flags, pcmk__xf_modified)) { flags = prefix; } else if (pcmk_is_set(p->flags, pcmk__xf_moved)) { flags = prefix_moved; } else { flags = prefix; } do_crm_log_alias(log_level, file, function, line, "%s %*s @%s=%s", flags, spaces, "", aname, value); } } free(prefix_moved); free(prefix_del); for (child = pcmk__xml_first_child(data); child != NULL; child = pcmk__xml_next(child)) { log_xml_changes(log_level, file, function, line, prefix, child, depth + 1, options); } pcmk__xml_log(log_level, file, function, line, prefix, data, depth, options|xml_log_option_close); } else { for (child = pcmk__xml_first_child(data); child != NULL; child = pcmk__xml_next(child)) { log_xml_changes(log_level, file, function, line, prefix, child, depth + 1, options); } } free(prefix_m); } void log_data_element(int log_level, const char *file, const char *function, int line, const char *prefix, const xmlNode *data, int depth, int options) { xmlNode *a_child = NULL; char *prefix_m = NULL; if (log_level == LOG_NEVER) { return; } if (prefix == NULL) { prefix = ""; } /* Since we use the same file and line, to avoid confusing libqb, we need to use the same format strings */ if (data == NULL) { do_crm_log_alias(log_level, file, function, line, "%s: %s", prefix, "No data to dump as XML"); return; } if (pcmk_is_set(options, xml_log_option_dirty_add)) { log_xml_changes(log_level, file, function, line, prefix, data, depth, options); return; } if (pcmk_is_set(options, xml_log_option_formatted)) { if (pcmk_is_set(options, xml_log_option_diff_plus) && (data->children == NULL || crm_element_value(data, XML_DIFF_MARKER))) { options |= xml_log_option_diff_all; prefix_m = strdup(prefix); prefix_m[1] = '+'; prefix = prefix_m; } else if (pcmk_is_set(options, xml_log_option_diff_minus) && (data->children == NULL || crm_element_value(data, XML_DIFF_MARKER))) { options |= xml_log_option_diff_all; prefix_m = strdup(prefix); prefix_m[1] = '-'; prefix = prefix_m; } } if (pcmk_is_set(options, xml_log_option_diff_short) && !pcmk_is_set(options, xml_log_option_diff_all)) { /* Still searching for the actual change */ for (a_child = pcmk__xml_first_child(data); a_child != NULL; a_child = pcmk__xml_next(a_child)) { log_data_element(log_level, file, function, line, prefix, a_child, depth + 1, options); } } else { pcmk__xml_log(log_level, file, function, line, prefix, data, depth, options|xml_log_option_open|xml_log_option_close |xml_log_option_children); } free(prefix_m); } /*! * \internal * \brief Append filtered XML attributes to a buffer * * \param[in] data XML element whose attributes to append * \param[in] options Group of \p xml_log_options flags * \param[in,out] buffer Where to append the content (must not be \p NULL) */ static void dump_filtered_xml(const xmlNode *data, int options, GString *buffer) { CRM_ASSERT(buffer != NULL); for (const xmlAttr *a = pcmk__xe_first_attr(data); a != NULL; a = a->next) { if (!pcmk__xa_filterable((const char *) (a->name))) { dump_xml_attr(a, options, buffer); } } } /*! * \internal * \brief Append a string representation of an XML element to a buffer * * \param[in] data XML whose representation to append * \param[in] options Group of \p xml_log_options flags * \param[in,out] buffer Where to append the content (must not be \p NULL) * \param[in] depth Current indentation level */ static void dump_xml_element(const xmlNode *data, int options, GString *buffer, int depth) { const char *name = NULL; CRM_ASSERT(buffer != NULL); if (data == NULL) { crm_trace("Nothing to dump"); return; } name = crm_element_name(data); CRM_ASSERT(name != NULL); insert_prefix(options, buffer, depth); g_string_append_printf(buffer, "<%s", name); if (options & xml_log_option_filtered) { dump_filtered_xml(data, options, buffer); } else { for (const xmlAttr *a = pcmk__xe_first_attr(data); a != NULL; a = a->next) { dump_xml_attr(a, options, buffer); } } if (data->children == NULL) { g_string_append(buffer, "/>"); } else { g_string_append_c(buffer, '>'); } if (pcmk_is_set(options, xml_log_option_formatted)) { g_string_append_c(buffer, '\n'); } if (data->children) { xmlNode *xChild = NULL; for(xChild = data->children; xChild != NULL; xChild = xChild->next) { pcmk__xml2text(xChild, options, buffer, depth + 1); } insert_prefix(options, buffer, depth); g_string_append_printf(buffer, "", name); if (pcmk_is_set(options, xml_log_option_formatted)) { g_string_append_c(buffer, '\n'); } } } /*! * \internal * \brief Append XML text content to a buffer * * \param[in] data XML whose content to append * \param[in] options Group of \p xml_log_options flags * \param[in,out] buffer Where to append the content (must not be \p NULL) * \param[in] depth Current indentation level */ static void dump_xml_text(const xmlNode *data, int options, GString *buffer, int depth) { CRM_ASSERT(buffer != NULL); if (data == NULL) { crm_trace("Nothing to dump"); return; } insert_prefix(options, buffer, depth); g_string_append(buffer, (const gchar *) data->content); if (pcmk_is_set(options, xml_log_option_formatted)) { g_string_append_c(buffer, '\n'); } } /*! * \internal * \brief Append XML CDATA content to a buffer * * \param[in] data XML whose content to append * \param[in] options Group of \p xml_log_options flags * \param[in,out] buffer Where to append the content (must not be \p NULL) * \param[in] depth Current indentation level */ static void dump_xml_cdata(const xmlNode *data, int options, GString *buffer, int depth) { CRM_ASSERT(buffer != NULL); if (data == NULL) { crm_trace("Nothing to dump"); return; } insert_prefix(options, buffer, depth); g_string_append_printf(buffer, "", (const gchar *) data->content); if (pcmk_is_set(options, xml_log_option_formatted)) { g_string_append_c(buffer, '\n'); } } /*! * \internal * \brief Append an XML comment to a buffer * * \param[in] data XML whose content to append * \param[in] options Group of \p xml_log_options flags * \param[in,out] buffer Where to append the content (must not be \p NULL) * \param[in] depth Current indentation level */ static void dump_xml_comment(const xmlNode *data, int options, GString *buffer, int depth) { CRM_ASSERT(buffer != NULL); if (data == NULL) { crm_trace("Nothing to dump"); return; } insert_prefix(options, buffer, depth); g_string_append_printf(buffer, "", (const gchar *) data->content); if (pcmk_is_set(options, xml_log_option_formatted)) { g_string_append_c(buffer, '\n'); } } #define PCMK__XMLDUMP_STATS 0 /*! * \internal * \brief Create a text representation of an XML object * * \param[in] data XML to convert * \param[in] options Group of \p xml_log_options flags * \param[in,out] buffer Where to store the text (must not be \p NULL) * \param[in] depth Current indentation level */ void pcmk__xml2text(xmlNodePtr data, int options, GString *buffer, int depth) { CRM_ASSERT(buffer != NULL); if (data == NULL) { return; } if (!pcmk_is_set(options, xml_log_option_filtered) && pcmk_is_set(options, xml_log_option_full_fledged)) { /* libxml's serialization reuse is a good idea, sadly we cannot apply it for the filtered cases (preceding filtering pass would preclude further reuse of such in-situ modified XML in generic context and is likely not a win performance-wise), and there's also a historically unstable throughput argument (likely stemming from memory allocation overhead, eventhough that shall be minimized with defaults preset in crm_xml_init) */ #if (PCMK__XMLDUMP_STATS - 0) time_t next, new = time(NULL); #endif xmlDoc *doc; xmlOutputBuffer *xml_buffer; doc = getDocPtr(data); /* doc will only be NULL if data is */ CRM_CHECK(doc != NULL, return); xml_buffer = xmlAllocOutputBuffer(NULL); CRM_ASSERT(xml_buffer != NULL); /* XXX we could setup custom allocation scheme for the particular buffer, but it's subsumed with crm_xml_init that needs to be invoked prior to entering this function as such, since its other branch vitally depends on it -- what can be done about this all is to have a facade parsing functions that would 100% mark entering libxml code for us, since we don't do anything as crazy as swapping out the binary form of the parsed tree (but those would need to be strictly used as opposed to libxml's raw functions) */ xmlNodeDumpOutput(xml_buffer, doc, data, 0, (options & xml_log_option_formatted), NULL); /* attempt adding final NL - failing shouldn't be fatal here */ (void) xmlOutputBufferWrite(xml_buffer, sizeof("\n") - 1, "\n"); if (xml_buffer->buffer != NULL) { g_string_append(buffer, (const gchar *) xmlBufContent(xml_buffer->buffer)); } #if (PCMK__XMLDUMP_STATS - 0) next = time(NULL); if ((now + 1) < next) { crm_log_xml_trace(data, "Long time"); crm_err("xmlNodeDumpOutput() -> %lld bytes took %ds", (long long) buffer->len, next - now); } #endif /* asserted allocation before so there should be something to remove */ (void) xmlOutputBufferClose(xml_buffer); return; } switch(data->type) { case XML_ELEMENT_NODE: /* Handle below */ dump_xml_element(data, options, buffer, depth); break; case XML_TEXT_NODE: /* if option xml_log_option_text is enabled, then dump XML_TEXT_NODE */ if (options & xml_log_option_text) { dump_xml_text(data, options, buffer, depth); } break; case XML_COMMENT_NODE: dump_xml_comment(data, options, buffer, depth); break; case XML_CDATA_SECTION_NODE: dump_xml_cdata(data, options, buffer, depth); break; default: crm_warn("Unhandled type: %d", data->type); break; /* XML_ATTRIBUTE_NODE = 2 XML_ENTITY_REF_NODE = 5 XML_ENTITY_NODE = 6 XML_PI_NODE = 7 XML_DOCUMENT_NODE = 9 XML_DOCUMENT_TYPE_NODE = 10 XML_DOCUMENT_FRAG_NODE = 11 XML_NOTATION_NODE = 12 XML_HTML_DOCUMENT_NODE = 13 XML_DTD_NODE = 14 XML_ELEMENT_DECL = 15 XML_ATTRIBUTE_DECL = 16 XML_ENTITY_DECL = 17 XML_NAMESPACE_DECL = 18 XML_XINCLUDE_START = 19 XML_XINCLUDE_END = 20 XML_DOCB_DOCUMENT_NODE = 21 */ } } char * dump_xml_formatted_with_text(xmlNode * an_xml_node) { char *buffer = NULL; GString *g_buffer = g_string_sized_new(1024); pcmk__xml2text(an_xml_node, xml_log_option_formatted|xml_log_option_full_fledged, g_buffer, 0); if (g_buffer != NULL) { buffer = strdup((const char *) g_buffer->str); g_string_free(g_buffer, TRUE); } return buffer; } char * dump_xml_formatted(xmlNode * an_xml_node) { char *buffer = NULL; GString *g_buffer = g_string_sized_new(1024); pcmk__xml2text(an_xml_node, xml_log_option_formatted, g_buffer, 0); if (g_buffer != NULL) { buffer = strdup((const char *) g_buffer->str); g_string_free(g_buffer, TRUE); } return buffer; } char * dump_xml_unformatted(xmlNode * an_xml_node) { char *buffer = NULL; GString *g_buffer = g_string_sized_new(1024); pcmk__xml2text(an_xml_node, 0, g_buffer, 0); if (g_buffer != NULL) { buffer = strdup((const char *) g_buffer->str); g_string_free(g_buffer, TRUE); } return buffer; } gboolean xml_has_children(const xmlNode * xml_root) { if (xml_root != NULL && xml_root->children != NULL) { return TRUE; } return FALSE; } void xml_remove_prop(xmlNode * obj, const char *name) { if (pcmk__check_acl(obj, NULL, pcmk__xf_acl_write) == FALSE) { crm_trace("Cannot remove %s from %s", name, obj->name); } else if (pcmk__tracking_xml_changes(obj, FALSE)) { /* Leave in place (marked for removal) until after the diff is calculated */ xml_private_t *p = NULL; xmlAttr *attr = xmlHasProp(obj, (pcmkXmlStr) name); p = attr->_private; set_parent_flag(obj, pcmk__xf_dirty); pcmk__set_xml_flags(p, pcmk__xf_deleted); } else { xmlUnsetProp(obj, (pcmkXmlStr) name); } } void save_xml_to_file(xmlNode * xml, const char *desc, const char *filename) { char *f = NULL; if (filename == NULL) { char *uuid = crm_generate_uuid(); f = crm_strdup_printf("%s/%s", pcmk__get_tmpdir(), uuid); filename = f; free(uuid); } crm_info("Saving %s to %s", desc, filename); write_xml_file(xml, filename, FALSE); free(f); } /*! * \internal * \brief Set a flag on all attributes of an XML element * * \param[in,out] xml XML node to set flags on * \param[in] flag XML private flag to set */ static void set_attrs_flag(xmlNode *xml, enum xml_private_flags flag) { for (xmlAttr *attr = pcmk__xe_first_attr(xml); attr; attr = attr->next) { pcmk__set_xml_flags((xml_private_t *) (attr->_private), flag); } } /*! * \internal * \brief Add an XML attribute to a node, marked as deleted * * When calculating XML changes, we need to know when an attribute has been * deleted. Add the attribute back to the new XML, so that we can check the * removal against ACLs, and mark it as deleted for later removal after * differences have been calculated. */ static void mark_attr_deleted(xmlNode *new_xml, const char *element, const char *attr_name, const char *old_value) { xml_private_t *p = new_xml->doc->_private; xmlAttr *attr = NULL; // Prevent the dirty flag being set recursively upwards pcmk__clear_xml_flags(p, pcmk__xf_tracking); // Restore the old value (and the tracking flag) attr = xmlSetProp(new_xml, (pcmkXmlStr) attr_name, (pcmkXmlStr) old_value); pcmk__set_xml_flags(p, pcmk__xf_tracking); // Reset flags (so the attribute doesn't appear as newly created) p = attr->_private; p->flags = 0; // Check ACLs and mark restored value for later removal xml_remove_prop(new_xml, attr_name); crm_trace("XML attribute %s=%s was removed from %s", attr_name, old_value, element); } /* * \internal * \brief Check ACLs for a changed XML attribute */ static void mark_attr_changed(xmlNode *new_xml, const char *element, const char *attr_name, const char *old_value) { char *vcopy = crm_element_value_copy(new_xml, attr_name); crm_trace("XML attribute %s was changed from '%s' to '%s' in %s", attr_name, old_value, vcopy, element); // Restore the original value xmlSetProp(new_xml, (pcmkXmlStr) attr_name, (pcmkXmlStr) old_value); // Change it back to the new value, to check ACLs crm_xml_add(new_xml, attr_name, vcopy); free(vcopy); } /*! * \internal * \brief Mark an XML attribute as having changed position */ static void mark_attr_moved(xmlNode *new_xml, const char *element, xmlAttr *old_attr, xmlAttr *new_attr, int p_old, int p_new) { xml_private_t *p = new_attr->_private; crm_trace("XML attribute %s moved from position %d to %d in %s", old_attr->name, p_old, p_new, element); // Mark document, element, and all element's parents as changed mark_xml_node_dirty(new_xml); // Mark attribute as changed pcmk__set_xml_flags(p, pcmk__xf_dirty|pcmk__xf_moved); p = (p_old > p_new)? old_attr->_private : new_attr->_private; pcmk__set_xml_flags(p, pcmk__xf_skip); } /*! * \internal * \brief Calculate differences in all previously existing XML attributes */ static void xml_diff_old_attrs(xmlNode *old_xml, xmlNode *new_xml) { xmlAttr *attr_iter = pcmk__xe_first_attr(old_xml); while (attr_iter != NULL) { xmlAttr *old_attr = attr_iter; xmlAttr *new_attr = xmlHasProp(new_xml, attr_iter->name); const char *name = (const char *) attr_iter->name; const char *old_value = crm_element_value(old_xml, name); attr_iter = attr_iter->next; if (new_attr == NULL) { mark_attr_deleted(new_xml, (const char *) old_xml->name, name, old_value); } else { xml_private_t *p = new_attr->_private; int new_pos = pcmk__xml_position((xmlNode*) new_attr, pcmk__xf_skip); int old_pos = pcmk__xml_position((xmlNode*) old_attr, pcmk__xf_skip); const char *new_value = crm_element_value(new_xml, name); // This attribute isn't new pcmk__clear_xml_flags(p, pcmk__xf_created); if (strcmp(new_value, old_value) != 0) { mark_attr_changed(new_xml, (const char *) old_xml->name, name, old_value); } else if ((old_pos != new_pos) && !pcmk__tracking_xml_changes(new_xml, TRUE)) { mark_attr_moved(new_xml, (const char *) old_xml->name, old_attr, new_attr, old_pos, new_pos); } } } } /*! * \internal * \brief Check all attributes in new XML for creation */ static void mark_created_attrs(xmlNode *new_xml) { xmlAttr *attr_iter = pcmk__xe_first_attr(new_xml); while (attr_iter != NULL) { xmlAttr *new_attr = attr_iter; xml_private_t *p = attr_iter->_private; attr_iter = attr_iter->next; if (pcmk_is_set(p->flags, pcmk__xf_created)) { const char *attr_name = (const char *) new_attr->name; crm_trace("Created new attribute %s=%s in %s", attr_name, crm_element_value(new_xml, attr_name), new_xml->name); /* Check ACLs (we can't use the remove-then-create trick because it * would modify the attribute position). */ if (pcmk__check_acl(new_xml, attr_name, pcmk__xf_acl_write)) { pcmk__mark_xml_attr_dirty(new_attr); } else { // Creation was not allowed, so remove the attribute xmlUnsetProp(new_xml, new_attr->name); } } } } /*! * \internal * \brief Calculate differences in attributes between two XML nodes */ static void xml_diff_attrs(xmlNode *old_xml, xmlNode *new_xml) { set_attrs_flag(new_xml, pcmk__xf_created); // cleared later if not really new xml_diff_old_attrs(old_xml, new_xml); mark_created_attrs(new_xml); } /*! * \internal * \brief Add an XML child element to a node, marked as deleted * * When calculating XML changes, we need to know when a child element has been * deleted. Add the child back to the new XML, so that we can check the removal * against ACLs, and mark it as deleted for later removal after differences have * been calculated. */ static void mark_child_deleted(xmlNode *old_child, xmlNode *new_parent) { // Re-create the child element so we can check ACLs xmlNode *candidate = add_node_copy(new_parent, old_child); // Clear flags on new child and its children reset_xml_node_flags(candidate); // Check whether ACLs allow the deletion pcmk__apply_acl(xmlDocGetRootElement(candidate->doc)); // Remove the child again (which will track it in document's deleted_objs) free_xml_with_position(candidate, pcmk__xml_position(old_child, pcmk__xf_skip)); if (pcmk__xml_match(new_parent, old_child, true) == NULL) { pcmk__set_xml_flags((xml_private_t *) (old_child->_private), pcmk__xf_skip); } } static void mark_child_moved(xmlNode *old_child, xmlNode *new_parent, xmlNode *new_child, int p_old, int p_new) { xml_private_t *p = new_child->_private; crm_trace("Child element %s with id='%s' moved from position %d to %d under %s", new_child->name, (ID(new_child)? ID(new_child) : ""), p_old, p_new, new_parent->name); mark_xml_node_dirty(new_parent); pcmk__set_xml_flags(p, pcmk__xf_moved); if (p_old > p_new) { p = old_child->_private; } else { p = new_child->_private; } pcmk__set_xml_flags(p, pcmk__xf_skip); } // Given original and new XML, mark new XML portions that have changed static void mark_xml_changes(xmlNode *old_xml, xmlNode *new_xml, bool check_top) { xmlNode *cIter = NULL; xml_private_t *p = NULL; CRM_CHECK(new_xml != NULL, return); if (old_xml == NULL) { pcmk__mark_xml_created(new_xml); pcmk__apply_creation_acl(new_xml, check_top); return; } p = new_xml->_private; CRM_CHECK(p != NULL, return); if(p->flags & pcmk__xf_processed) { /* Avoid re-comparing nodes */ return; } pcmk__set_xml_flags(p, pcmk__xf_processed); xml_diff_attrs(old_xml, new_xml); // Check for differences in the original children for (cIter = pcmk__xml_first_child(old_xml); cIter != NULL; ) { xmlNode *old_child = cIter; xmlNode *new_child = pcmk__xml_match(new_xml, cIter, true); cIter = pcmk__xml_next(cIter); if(new_child) { mark_xml_changes(old_child, new_child, TRUE); } else { mark_child_deleted(old_child, new_xml); } } // Check for moved or created children for (cIter = pcmk__xml_first_child(new_xml); cIter != NULL; ) { xmlNode *new_child = cIter; xmlNode *old_child = pcmk__xml_match(old_xml, cIter, true); cIter = pcmk__xml_next(cIter); if(old_child == NULL) { // This is a newly created child p = new_child->_private; pcmk__set_xml_flags(p, pcmk__xf_skip); mark_xml_changes(old_child, new_child, TRUE); } else { /* Check for movement, we already checked for differences */ int p_new = pcmk__xml_position(new_child, pcmk__xf_skip); int p_old = pcmk__xml_position(old_child, pcmk__xf_skip); if(p_old != p_new) { mark_child_moved(old_child, new_xml, new_child, p_old, p_new); } } } } void xml_calculate_significant_changes(xmlNode *old_xml, xmlNode *new_xml) { pcmk__set_xml_doc_flag(new_xml, pcmk__xf_lazy); xml_calculate_changes(old_xml, new_xml); } void xml_calculate_changes(xmlNode *old_xml, xmlNode *new_xml) { CRM_CHECK(pcmk__str_eq(crm_element_name(old_xml), crm_element_name(new_xml), pcmk__str_casei), return); CRM_CHECK(pcmk__str_eq(ID(old_xml), ID(new_xml), pcmk__str_casei), return); if(xml_tracking_changes(new_xml) == FALSE) { xml_track_changes(new_xml, NULL, NULL, FALSE); } mark_xml_changes(old_xml, new_xml, FALSE); } gboolean can_prune_leaf(xmlNode * xml_node) { xmlNode *cIter = NULL; gboolean can_prune = TRUE; const char *name = crm_element_name(xml_node); if (pcmk__strcase_any_of(name, XML_TAG_RESOURCE_REF, XML_CIB_TAG_OBJ_REF, XML_ACL_TAG_ROLE_REF, XML_ACL_TAG_ROLE_REFv1, NULL)) { return FALSE; } for (xmlAttrPtr a = pcmk__xe_first_attr(xml_node); a != NULL; a = a->next) { const char *p_name = (const char *) a->name; if (strcmp(p_name, XML_ATTR_ID) == 0) { continue; } can_prune = FALSE; } cIter = pcmk__xml_first_child(xml_node); while (cIter) { xmlNode *child = cIter; cIter = pcmk__xml_next(cIter); if (can_prune_leaf(child)) { free_xml(child); } else { can_prune = FALSE; } } return can_prune; } /*! * \internal * \brief Find a comment with matching content in specified XML * * \param[in] root XML to search * \param[in] search_comment Comment whose content should be searched for * \param[in] exact If true, comment must also be at same position */ xmlNode * pcmk__xc_match(xmlNode *root, xmlNode *search_comment, bool exact) { xmlNode *a_child = NULL; int search_offset = pcmk__xml_position(search_comment, pcmk__xf_skip); CRM_CHECK(search_comment->type == XML_COMMENT_NODE, return NULL); for (a_child = pcmk__xml_first_child(root); a_child != NULL; a_child = pcmk__xml_next(a_child)) { if (exact) { int offset = pcmk__xml_position(a_child, pcmk__xf_skip); xml_private_t *p = a_child->_private; if (offset < search_offset) { continue; } else if (offset > search_offset) { return NULL; } if (pcmk_is_set(p->flags, pcmk__xf_skip)) { continue; } } if (a_child->type == XML_COMMENT_NODE && pcmk__str_eq((const char *)a_child->content, (const char *)search_comment->content, pcmk__str_casei)) { return a_child; } else if (exact) { return NULL; } } return NULL; } /*! * \internal * \brief Make one XML comment match another (in content) * * \param[in,out] parent If \p target is NULL and this is not, add or update * comment child of this XML node that matches \p update * \param[in,out] target If not NULL, update this XML comment node * \param[in] update Make comment content match this (must not be NULL) * * \note At least one of \parent and \target must be non-NULL */ void pcmk__xc_update(xmlNode *parent, xmlNode *target, xmlNode *update) { CRM_CHECK(update != NULL, return); CRM_CHECK(update->type == XML_COMMENT_NODE, return); if (target == NULL) { target = pcmk__xc_match(parent, update, false); } if (target == NULL) { add_node_copy(parent, update); } else if (!pcmk__str_eq((const char *)target->content, (const char *)update->content, pcmk__str_casei)) { xmlFree(target->content); target->content = xmlStrdup(update->content); } } /*! * \internal * \brief Make one XML tree match another (in children and attributes) * * \param[in,out] parent If \p target is NULL and this is not, add or update * child of this XML node that matches \p update * \param[in,out] target If not NULL, update this XML * \param[in] update Make the desired XML match this (must not be NULL) * \param[in] as_diff If false, expand "++" when making attributes match * * \note At least one of \parent and \target must be non-NULL */ void pcmk__xml_update(xmlNode *parent, xmlNode *target, xmlNode *update, bool as_diff) { xmlNode *a_child = NULL; const char *object_name = NULL, *object_href = NULL, *object_href_val = NULL; #if XML_PARSER_DEBUG crm_log_xml_trace(update, "update:"); crm_log_xml_trace(target, "target:"); #endif CRM_CHECK(update != NULL, return); if (update->type == XML_COMMENT_NODE) { pcmk__xc_update(parent, target, update); return; } object_name = crm_element_name(update); object_href_val = ID(update); if (object_href_val != NULL) { object_href = XML_ATTR_ID; } else { object_href_val = crm_element_value(update, XML_ATTR_IDREF); object_href = (object_href_val == NULL) ? NULL : XML_ATTR_IDREF; } CRM_CHECK(object_name != NULL, return); CRM_CHECK(target != NULL || parent != NULL, return); if (target == NULL) { target = pcmk__xe_match(parent, object_name, object_href, object_href_val); } if (target == NULL) { target = create_xml_node(parent, object_name); CRM_CHECK(target != NULL, return); #if XML_PARSER_DEBUG crm_trace("Added <%s%s%s%s%s/>", pcmk__s(object_name, ""), object_href ? " " : "", object_href ? object_href : "", object_href ? "=" : "", object_href ? object_href_val : ""); } else { crm_trace("Found node <%s%s%s%s%s/> to update", pcmk__s(object_name, ""), object_href ? " " : "", object_href ? object_href : "", object_href ? "=" : "", object_href ? object_href_val : ""); #endif } CRM_CHECK(pcmk__str_eq(crm_element_name(target), crm_element_name(update), pcmk__str_casei), return); if (as_diff == FALSE) { /* So that expand_plus_plus() gets called */ copy_in_properties(target, update); } else { /* No need for expand_plus_plus(), just raw speed */ for (xmlAttrPtr a = pcmk__xe_first_attr(update); a != NULL; a = a->next) { const char *p_value = pcmk__xml_attr_value(a); /* Remove it first so the ordering of the update is preserved */ xmlUnsetProp(target, a->name); xmlSetProp(target, a->name, (pcmkXmlStr) p_value); } } for (a_child = pcmk__xml_first_child(update); a_child != NULL; a_child = pcmk__xml_next(a_child)) { #if XML_PARSER_DEBUG crm_trace("Updating child <%s%s%s%s%s/>", pcmk__s(object_name, ""), object_href ? " " : "", object_href ? object_href : "", object_href ? "=" : "", object_href ? object_href_val : ""); #endif pcmk__xml_update(target, NULL, a_child, as_diff); } #if XML_PARSER_DEBUG crm_trace("Finished with <%s%s%s%s%s/>", pcmk__s(object_name, ""), object_href ? " " : "", object_href ? object_href : "", object_href ? "=" : "", object_href ? object_href_val : ""); #endif } gboolean update_xml_child(xmlNode * child, xmlNode * to_update) { gboolean can_update = TRUE; xmlNode *child_of_child = NULL; CRM_CHECK(child != NULL, return FALSE); CRM_CHECK(to_update != NULL, return FALSE); if (!pcmk__str_eq(crm_element_name(to_update), crm_element_name(child), pcmk__str_none)) { can_update = FALSE; } else if (!pcmk__str_eq(ID(to_update), ID(child), pcmk__str_none)) { can_update = FALSE; } else if (can_update) { #if XML_PARSER_DEBUG crm_log_xml_trace(child, "Update match found..."); #endif pcmk__xml_update(NULL, child, to_update, false); } for (child_of_child = pcmk__xml_first_child(child); child_of_child != NULL; child_of_child = pcmk__xml_next(child_of_child)) { /* only update the first one */ if (can_update) { break; } can_update = update_xml_child(child_of_child, to_update); } return can_update; } int find_xml_children(xmlNode ** children, xmlNode * root, const char *tag, const char *field, const char *value, gboolean search_matches) { int match_found = 0; CRM_CHECK(root != NULL, return FALSE); CRM_CHECK(children != NULL, return FALSE); if (tag != NULL && !pcmk__str_eq(tag, crm_element_name(root), pcmk__str_casei)) { } else if (value != NULL && !pcmk__str_eq(value, crm_element_value(root, field), pcmk__str_casei)) { } else { if (*children == NULL) { *children = create_xml_node(NULL, __func__); } add_node_copy(*children, root); match_found = 1; } if (search_matches || match_found == 0) { xmlNode *child = NULL; for (child = pcmk__xml_first_child(root); child != NULL; child = pcmk__xml_next(child)) { match_found += find_xml_children(children, child, tag, field, value, search_matches); } } return match_found; } gboolean replace_xml_child(xmlNode * parent, xmlNode * child, xmlNode * update, gboolean delete_only) { gboolean can_delete = FALSE; xmlNode *child_of_child = NULL; const char *up_id = NULL; const char *child_id = NULL; const char *right_val = NULL; CRM_CHECK(child != NULL, return FALSE); CRM_CHECK(update != NULL, return FALSE); up_id = ID(update); child_id = ID(child); if (up_id == NULL || (child_id && strcmp(child_id, up_id) == 0)) { can_delete = TRUE; } if (!pcmk__str_eq(crm_element_name(update), crm_element_name(child), pcmk__str_casei)) { can_delete = FALSE; } if (can_delete && delete_only) { for (xmlAttrPtr a = pcmk__xe_first_attr(update); a != NULL; a = a->next) { const char *p_name = (const char *) a->name; const char *p_value = pcmk__xml_attr_value(a); right_val = crm_element_value(child, p_name); if (!pcmk__str_eq(p_value, right_val, pcmk__str_casei)) { can_delete = FALSE; } } } if (can_delete && parent != NULL) { crm_log_xml_trace(child, "Delete match found..."); if (delete_only || update == NULL) { free_xml(child); } else { xmlNode *tmp = copy_xml(update); xmlDoc *doc = tmp->doc; xmlNode *old = NULL; xml_accept_changes(tmp); old = xmlReplaceNode(child, tmp); if(xml_tracking_changes(tmp)) { /* Replaced sections may have included relevant ACLs */ pcmk__apply_acl(tmp); } xml_calculate_changes(old, tmp); xmlDocSetRootElement(doc, old); free_xml(old); } child = NULL; return TRUE; } else if (can_delete) { crm_log_xml_debug(child, "Cannot delete the search root"); can_delete = FALSE; } child_of_child = pcmk__xml_first_child(child); while (child_of_child) { xmlNode *next = pcmk__xml_next(child_of_child); can_delete = replace_xml_child(child, child_of_child, update, delete_only); /* only delete the first one */ if (can_delete) { child_of_child = NULL; } else { child_of_child = next; } } return can_delete; } xmlNode * sorted_xml(xmlNode *input, xmlNode *parent, gboolean recursive) { xmlNode *child = NULL; GSList *nvpairs = NULL; xmlNode *result = NULL; const char *name = NULL; CRM_CHECK(input != NULL, return NULL); name = crm_element_name(input); CRM_CHECK(name != NULL, return NULL); result = create_xml_node(parent, name); nvpairs = pcmk_xml_attrs2nvpairs(input); nvpairs = pcmk_sort_nvpairs(nvpairs); pcmk_nvpairs2xml_attrs(nvpairs, result); pcmk_free_nvpairs(nvpairs); for (child = pcmk__xml_first_child(input); child != NULL; child = pcmk__xml_next(child)) { if (recursive) { sorted_xml(child, result, recursive); } else { add_node_copy(result, child); } } return result; } xmlNode * first_named_child(const xmlNode *parent, const char *name) { xmlNode *match = NULL; for (match = pcmk__xe_first_child(parent); match != NULL; match = pcmk__xe_next(match)) { /* * name == NULL gives first child regardless of name; this is * semantically incorrect in this function, but may be necessary * due to prior use of xml_child_iter_filter */ if (pcmk__str_eq(name, (const char *)match->name, pcmk__str_null_matches)) { return match; } } return NULL; } /*! * \brief Get next instance of same XML tag * * \param[in] sibling XML tag to start from * * \return Next sibling XML tag with same name */ xmlNode * crm_next_same_xml(const xmlNode *sibling) { xmlNode *match = pcmk__xe_next(sibling); const char *name = crm_element_name(sibling); while (match != NULL) { if (!strcmp(crm_element_name(match), name)) { return match; } match = pcmk__xe_next(match); } return NULL; } void crm_xml_init(void) { static bool init = true; if(init) { init = false; /* The default allocator XML_BUFFER_ALLOC_EXACT does far too many * pcmk__realloc()s and it can take upwards of 18 seconds (yes, seconds) * to dump a 28kb tree which XML_BUFFER_ALLOC_DOUBLEIT can do in * less than 1 second. */ xmlSetBufferAllocationScheme(XML_BUFFER_ALLOC_DOUBLEIT); /* Populate and free the _private field when nodes are created and destroyed */ xmlDeregisterNodeDefault(free_private_data); xmlRegisterNodeDefault(new_private_data); crm_schema_init(); } } void crm_xml_cleanup(void) { crm_schema_cleanup(); xmlCleanupParser(); } #define XPATH_MAX 512 xmlNode * expand_idref(xmlNode * input, xmlNode * top) { const char *tag = NULL; const char *ref = NULL; xmlNode *result = input; if (result == NULL) { return NULL; } else if (top == NULL) { top = input; } tag = crm_element_name(result); ref = crm_element_value(result, XML_ATTR_IDREF); if (ref != NULL) { char *xpath_string = crm_strdup_printf("//%s[@id='%s']", tag, ref); result = get_xpath_object(xpath_string, top, LOG_ERR); if (result == NULL) { char *nodePath = (char *)xmlGetNodePath(top); crm_err("No match for %s found in %s: Invalid configuration", xpath_string, pcmk__s(nodePath, "unrecognizable path")); free(nodePath); } free(xpath_string); } return result; } -void -crm_destroy_xml(gpointer data) -{ - free_xml(data); -} - char * pcmk__xml_artefact_root(enum pcmk__xml_artefact_ns ns) { static const char *base = NULL; char *ret = NULL; if (base == NULL) { base = getenv("PCMK_schema_directory"); } if (pcmk__str_empty(base)) { base = CRM_SCHEMA_DIRECTORY; } switch (ns) { case pcmk__xml_artefact_ns_legacy_rng: case pcmk__xml_artefact_ns_legacy_xslt: ret = strdup(base); break; case pcmk__xml_artefact_ns_base_rng: case pcmk__xml_artefact_ns_base_xslt: ret = crm_strdup_printf("%s/base", base); break; default: crm_err("XML artefact family specified as %u not recognized", ns); } return ret; } char * pcmk__xml_artefact_path(enum pcmk__xml_artefact_ns ns, const char *filespec) { char *base = pcmk__xml_artefact_root(ns), *ret = NULL; switch (ns) { case pcmk__xml_artefact_ns_legacy_rng: case pcmk__xml_artefact_ns_base_rng: ret = crm_strdup_printf("%s/%s.rng", base, filespec); break; case pcmk__xml_artefact_ns_legacy_xslt: case pcmk__xml_artefact_ns_base_xslt: ret = crm_strdup_printf("%s/%s.xsl", base, filespec); break; default: crm_err("XML artefact family specified as %u not recognized", ns); } free(base); return ret; } void pcmk__xe_set_propv(xmlNodePtr node, va_list pairs) { while (true) { const char *name, *value; name = va_arg(pairs, const char *); if (name == NULL) { return; } value = va_arg(pairs, const char *); if (value != NULL) { crm_xml_add(node, name, value); } } } void pcmk__xe_set_props(xmlNodePtr node, ...) { va_list pairs; va_start(pairs, node); pcmk__xe_set_propv(node, pairs); va_end(pairs); } int pcmk__xe_foreach_child(xmlNode *xml, const char *child_element_name, int (*handler)(xmlNode *xml, void *userdata), void *userdata) { xmlNode *children = (xml? xml->children : NULL); CRM_ASSERT(handler != NULL); for (xmlNode *node = children; node != NULL; node = node->next) { if (node->type == XML_ELEMENT_NODE && pcmk__str_eq(child_element_name, (const char *) node->name, pcmk__str_null_matches)) { int rc = handler(node, userdata); if (rc != pcmk_rc_ok) { return rc; } } } return pcmk_rc_ok; } // Deprecated functions kept only for backward API compatibility // LCOV_EXCL_START #include xmlNode * find_entity(xmlNode *parent, const char *node_name, const char *id) { return pcmk__xe_match(parent, node_name, ((id == NULL)? id : XML_ATTR_ID), id); } +void +crm_destroy_xml(gpointer data) +{ + free_xml(data); +} + // LCOV_EXCL_STOP // End deprecated API diff --git a/lib/fencing/st_client.c b/lib/fencing/st_client.c index 8a5503d7fb..3754926540 100644 --- a/lib/fencing/st_client.c +++ b/lib/fencing/st_client.c @@ -1,2699 +1,2701 @@ /* * Copyright 2004-2022 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "fencing_private.h" CRM_TRACE_INIT_DATA(stonith); // Used as stonith_t:st_private typedef struct stonith_private_s { char *token; crm_ipc_t *ipc; mainloop_io_t *source; GHashTable *stonith_op_callback_table; GList *notify_list; int notify_refcnt; bool notify_deletes; void (*op_callback) (stonith_t * st, stonith_callback_data_t * data); } stonith_private_t; // Used as stonith_event_t:opaque struct event_private { pcmk__action_result_t result; }; typedef struct stonith_notify_client_s { const char *event; const char *obj_id; /* implement one day */ const char *obj_type; /* implement one day */ void (*notify) (stonith_t * st, stonith_event_t * e); bool delete; } stonith_notify_client_t; typedef struct stonith_callback_client_s { void (*callback) (stonith_t * st, stonith_callback_data_t * data); const char *id; void *user_data; gboolean only_success; gboolean allow_timeout_updates; struct timer_rec_s *timer; } stonith_callback_client_t; struct notify_blob_s { stonith_t *stonith; xmlNode *xml; }; struct timer_rec_s { int call_id; int timeout; guint ref; stonith_t *stonith; }; typedef int (*stonith_op_t) (const char *, int, const char *, xmlNode *, xmlNode *, xmlNode *, xmlNode **, xmlNode **); bool stonith_dispatch(stonith_t * st); xmlNode *stonith_create_op(int call_id, const char *token, const char *op, xmlNode * data, int call_options); static int stonith_send_command(stonith_t *stonith, const char *op, xmlNode *data, xmlNode **output_data, int call_options, int timeout); static void stonith_connection_destroy(gpointer user_data); static void stonith_send_notification(gpointer data, gpointer user_data); static int stonith_api_del_notification(stonith_t *stonith, const char *event); /*! * \brief Get agent namespace by name * * \param[in] namespace_s Name of namespace as string * * \return Namespace as enum value */ enum stonith_namespace stonith_text2namespace(const char *namespace_s) { if (pcmk__str_eq(namespace_s, "any", pcmk__str_null_matches)) { return st_namespace_any; } else if (!strcmp(namespace_s, "redhat") || !strcmp(namespace_s, "stonith-ng")) { return st_namespace_rhcs; } else if (!strcmp(namespace_s, "internal")) { return st_namespace_internal; } else if (!strcmp(namespace_s, "heartbeat")) { return st_namespace_lha; } return st_namespace_invalid; } /*! * \brief Get agent namespace name * * \param[in] namespace Namespace as enum value * * \return Namespace name as string */ const char * stonith_namespace2text(enum stonith_namespace st_namespace) { switch (st_namespace) { case st_namespace_any: return "any"; case st_namespace_rhcs: return "stonith-ng"; case st_namespace_internal: return "internal"; case st_namespace_lha: return "heartbeat"; default: break; } return "unsupported"; } /*! * \brief Determine namespace of a fence agent * * \param[in] agent Fence agent type * \param[in] namespace_s Name of agent namespace as string, if known * * \return Namespace of specified agent, as enum value */ enum stonith_namespace stonith_get_namespace(const char *agent, const char *namespace_s) { if (pcmk__str_eq(namespace_s, "internal", pcmk__str_none)) { return st_namespace_internal; } if (stonith__agent_is_rhcs(agent)) { return st_namespace_rhcs; } #if HAVE_STONITH_STONITH_H if (stonith__agent_is_lha(agent)) { return st_namespace_lha; } #endif crm_err("Unknown fence agent: %s", agent); return st_namespace_invalid; } gboolean stonith__watchdog_fencing_enabled_for_node_api(stonith_t *st, const char *node) { gboolean rv = FALSE; stonith_t *stonith_api = st?st:stonith_api_new(); char *list = NULL; if(stonith_api) { if (stonith_api->state == stonith_disconnected) { int rc = stonith_api->cmds->connect(stonith_api, "stonith-api", NULL); if (rc != pcmk_ok) { crm_err("Failed connecting to Stonith-API for watchdog-fencing-query."); } } if (stonith_api->state != stonith_disconnected) { /* caveat!!! * this might fail when when stonithd is just updating the device-list * probably something we should fix as well for other api-calls */ int rc = stonith_api->cmds->list(stonith_api, st_opt_sync_call, STONITH_WATCHDOG_ID, &list, 0); if ((rc != pcmk_ok) || (list == NULL)) { /* due to the race described above it can happen that * we drop in here - so as not to make remote nodes * panic on that answer */ if (rc == -ENODEV) { crm_notice("Cluster does not have watchdog fencing device"); } else { crm_warn("Could not check for watchdog fencing device: %s", pcmk_strerror(rc)); } } else if (list[0] == '\0') { rv = TRUE; } else { GList *targets = stonith__parse_targets(list); rv = pcmk__str_in_list(node, targets, pcmk__str_casei); g_list_free_full(targets, free); } free(list); if (!st) { /* if we're provided the api we still might have done the * connection - but let's assume the caller won't bother */ stonith_api->cmds->disconnect(stonith_api); } } if (!st) { stonith_api_delete(stonith_api); } } else { crm_err("Stonith-API for watchdog-fencing-query couldn't be created."); } crm_trace("Pacemaker assumes node %s %sto do watchdog-fencing.", node, rv?"":"not "); return rv; } gboolean stonith__watchdog_fencing_enabled_for_node(const char *node) { return stonith__watchdog_fencing_enabled_for_node_api(NULL, node); } /* when cycling through the list we don't want to delete items so just mark them and when we know nobody is using the list loop over it to remove the marked items */ static void foreach_notify_entry (stonith_private_t *private, GFunc func, gpointer user_data) { private->notify_refcnt++; g_list_foreach(private->notify_list, func, user_data); private->notify_refcnt--; if ((private->notify_refcnt == 0) && private->notify_deletes) { GList *list_item = private->notify_list; private->notify_deletes = FALSE; while (list_item != NULL) { stonith_notify_client_t *list_client = list_item->data; GList *next = g_list_next(list_item); if (list_client->delete) { free(list_client); private->notify_list = g_list_delete_link(private->notify_list, list_item); } list_item = next; } } } static void stonith_connection_destroy(gpointer user_data) { stonith_t *stonith = user_data; stonith_private_t *native = NULL; struct notify_blob_s blob; crm_trace("Sending destroyed notification"); blob.stonith = stonith; blob.xml = create_xml_node(NULL, "notify"); native = stonith->st_private; native->ipc = NULL; native->source = NULL; free(native->token); native->token = NULL; stonith->state = stonith_disconnected; crm_xml_add(blob.xml, F_TYPE, T_STONITH_NOTIFY); crm_xml_add(blob.xml, F_SUBTYPE, T_STONITH_NOTIFY_DISCONNECT); foreach_notify_entry(native, stonith_send_notification, &blob); free_xml(blob.xml); } xmlNode * create_device_registration_xml(const char *id, enum stonith_namespace namespace, - const char *agent, stonith_key_value_t *params, + const char *agent, + const stonith_key_value_t *params, const char *rsc_provides) { xmlNode *data = create_xml_node(NULL, F_STONITH_DEVICE); xmlNode *args = create_xml_node(data, XML_TAG_ATTRS); #if HAVE_STONITH_STONITH_H if (namespace == st_namespace_any) { namespace = stonith_get_namespace(agent, NULL); } if (namespace == st_namespace_lha) { hash2field((gpointer) "plugin", (gpointer) agent, args); agent = "fence_legacy"; } #endif crm_xml_add(data, XML_ATTR_ID, id); crm_xml_add(data, F_STONITH_ORIGIN, __func__); crm_xml_add(data, "agent", agent); if ((namespace != st_namespace_any) && (namespace != st_namespace_invalid)) { crm_xml_add(data, "namespace", stonith_namespace2text(namespace)); } if (rsc_provides) { crm_xml_add(data, "rsc_provides", rsc_provides); } for (; params; params = params->next) { hash2field((gpointer) params->key, (gpointer) params->value, args); } return data; } static int -stonith_api_register_device(stonith_t * st, int call_options, - const char *id, const char *namespace, const char *agent, - stonith_key_value_t * params) +stonith_api_register_device(stonith_t *st, int call_options, + const char *id, const char *namespace, + const char *agent, + const stonith_key_value_t *params) { int rc = 0; xmlNode *data = NULL; data = create_device_registration_xml(id, stonith_text2namespace(namespace), agent, params, NULL); rc = stonith_send_command(st, STONITH_OP_DEVICE_ADD, data, NULL, call_options, 0); free_xml(data); return rc; } static int stonith_api_remove_device(stonith_t * st, int call_options, const char *name) { int rc = 0; xmlNode *data = NULL; data = create_xml_node(NULL, F_STONITH_DEVICE); crm_xml_add(data, F_STONITH_ORIGIN, __func__); crm_xml_add(data, XML_ATTR_ID, name); rc = stonith_send_command(st, STONITH_OP_DEVICE_DEL, data, NULL, call_options, 0); free_xml(data); return rc; } static int stonith_api_remove_level_full(stonith_t *st, int options, const char *node, const char *pattern, const char *attr, const char *value, int level) { int rc = 0; xmlNode *data = NULL; CRM_CHECK(node || pattern || (attr && value), return -EINVAL); data = create_xml_node(NULL, XML_TAG_FENCING_LEVEL); crm_xml_add(data, F_STONITH_ORIGIN, __func__); if (node) { crm_xml_add(data, XML_ATTR_STONITH_TARGET, node); } else if (pattern) { crm_xml_add(data, XML_ATTR_STONITH_TARGET_PATTERN, pattern); } else { crm_xml_add(data, XML_ATTR_STONITH_TARGET_ATTRIBUTE, attr); crm_xml_add(data, XML_ATTR_STONITH_TARGET_VALUE, value); } crm_xml_add_int(data, XML_ATTR_STONITH_INDEX, level); rc = stonith_send_command(st, STONITH_OP_LEVEL_DEL, data, NULL, options, 0); free_xml(data); return rc; } static int stonith_api_remove_level(stonith_t * st, int options, const char *node, int level) { return stonith_api_remove_level_full(st, options, node, NULL, NULL, NULL, level); } /*! * \internal * \brief Create XML for fence topology level registration request * * \param[in] node If not NULL, target level by this node name * \param[in] pattern If not NULL, target by node name using this regex * \param[in] attr If not NULL, target by this node attribute * \param[in] value If not NULL, target by this node attribute value * \param[in] level Index number of level to register * \param[in] device_list List of devices in level * * \return Newly allocated XML tree on success, NULL otherwise * * \note The caller should set only one of node, pattern or attr/value. */ xmlNode * create_level_registration_xml(const char *node, const char *pattern, const char *attr, const char *value, - int level, stonith_key_value_t *device_list) + int level, const stonith_key_value_t *device_list) { size_t len = 0; char *list = NULL; xmlNode *data; CRM_CHECK(node || pattern || (attr && value), return NULL); data = create_xml_node(NULL, XML_TAG_FENCING_LEVEL); CRM_CHECK(data, return NULL); crm_xml_add(data, F_STONITH_ORIGIN, __func__); crm_xml_add_int(data, XML_ATTR_ID, level); crm_xml_add_int(data, XML_ATTR_STONITH_INDEX, level); if (node) { crm_xml_add(data, XML_ATTR_STONITH_TARGET, node); } else if (pattern) { crm_xml_add(data, XML_ATTR_STONITH_TARGET_PATTERN, pattern); } else { crm_xml_add(data, XML_ATTR_STONITH_TARGET_ATTRIBUTE, attr); crm_xml_add(data, XML_ATTR_STONITH_TARGET_VALUE, value); } // cppcheck seems not to understand the abort logic behind pcmk__realloc // cppcheck-suppress memleak for (; device_list; device_list = device_list->next) { pcmk__add_separated_word(&list, &len, device_list->value, ","); } crm_xml_add(data, XML_ATTR_STONITH_DEVICES, list); free(list); return data; } static int -stonith_api_register_level_full(stonith_t * st, int options, const char *node, - const char *pattern, - const char *attr, const char *value, - int level, stonith_key_value_t *device_list) +stonith_api_register_level_full(stonith_t *st, int options, const char *node, + const char *pattern, const char *attr, + const char *value, int level, + const stonith_key_value_t *device_list) { int rc = 0; xmlNode *data = create_level_registration_xml(node, pattern, attr, value, level, device_list); CRM_CHECK(data != NULL, return -EINVAL); rc = stonith_send_command(st, STONITH_OP_LEVEL_ADD, data, NULL, options, 0); free_xml(data); return rc; } static int stonith_api_register_level(stonith_t * st, int options, const char *node, int level, - stonith_key_value_t * device_list) + const stonith_key_value_t * device_list) { return stonith_api_register_level_full(st, options, node, NULL, NULL, NULL, level, device_list); } static int stonith_api_device_list(stonith_t * stonith, int call_options, const char *namespace, stonith_key_value_t ** devices, int timeout) { int count = 0; enum stonith_namespace ns = stonith_text2namespace(namespace); if (devices == NULL) { crm_err("Parameter error: stonith_api_device_list"); return -EFAULT; } #if HAVE_STONITH_STONITH_H // Include Linux-HA agents if requested if ((ns == st_namespace_any) || (ns == st_namespace_lha)) { count += stonith__list_lha_agents(devices); } #endif // Include Red Hat agents if requested if ((ns == st_namespace_any) || (ns == st_namespace_rhcs)) { count += stonith__list_rhcs_agents(devices); } return count; } // See stonith_api_operations_t:metadata() documentation static int stonith_api_device_metadata(stonith_t *stonith, int call_options, const char *agent, const char *namespace, char **output, int timeout_sec) { /* By executing meta-data directly, we can get it from stonith_admin when * the cluster is not running, which is important for higher-level tools. */ enum stonith_namespace ns = stonith_get_namespace(agent, namespace); if (timeout_sec <= 0) { timeout_sec = CRMD_METADATA_CALL_TIMEOUT; } crm_trace("Looking up metadata for %s agent %s", stonith_namespace2text(ns), agent); switch (ns) { case st_namespace_rhcs: return stonith__rhcs_metadata(agent, timeout_sec, output); #if HAVE_STONITH_STONITH_H case st_namespace_lha: return stonith__lha_metadata(agent, timeout_sec, output); #endif default: crm_err("Can't get fence agent '%s' meta-data: No such agent", agent); break; } return -ENODEV; } static int stonith_api_query(stonith_t * stonith, int call_options, const char *target, stonith_key_value_t ** devices, int timeout) { int rc = 0, lpc = 0, max = 0; xmlNode *data = NULL; xmlNode *output = NULL; xmlXPathObjectPtr xpathObj = NULL; CRM_CHECK(devices != NULL, return -EINVAL); data = create_xml_node(NULL, F_STONITH_DEVICE); crm_xml_add(data, F_STONITH_ORIGIN, __func__); crm_xml_add(data, F_STONITH_TARGET, target); crm_xml_add(data, F_STONITH_ACTION, "off"); rc = stonith_send_command(stonith, STONITH_OP_QUERY, data, &output, call_options, timeout); if (rc < 0) { return rc; } xpathObj = xpath_search(output, "//@agent"); if (xpathObj) { max = numXpathResults(xpathObj); for (lpc = 0; lpc < max; lpc++) { xmlNode *match = getXpathResult(xpathObj, lpc); CRM_LOG_ASSERT(match != NULL); if(match != NULL) { xmlChar *match_path = xmlGetNodePath(match); crm_info("%s[%d] = %s", "//@agent", lpc, match_path); free(match_path); *devices = stonith_key_value_add(*devices, NULL, crm_element_value(match, XML_ATTR_ID)); } } freeXpathObject(xpathObj); } free_xml(output); free_xml(data); return max; } /*! * \internal * \brief Make a STONITH_OP_EXEC request * * \param[in] stonith Fencer connection * \param[in] call_options Bitmask of \c stonith_call_options * \param[in] id Fence device ID that request is for * \param[in] action Agent action to request (list, status, or monitor) * \param[in] target Name of target node for requested action * \param[in] timeout_sec Error if not completed within this many seconds * \param[out] output Where to set agent output */ static int stonith_api_call(stonith_t *stonith, int call_options, const char *id, const char *action, const char *target, int timeout_sec, xmlNode **output) { int rc = 0; xmlNode *data = NULL; data = create_xml_node(NULL, F_STONITH_DEVICE); crm_xml_add(data, F_STONITH_ORIGIN, __func__); crm_xml_add(data, F_STONITH_DEVICE, id); crm_xml_add(data, F_STONITH_ACTION, action); crm_xml_add(data, F_STONITH_TARGET, target); rc = stonith_send_command(stonith, STONITH_OP_EXEC, data, output, call_options, timeout_sec); free_xml(data); return rc; } static int stonith_api_list(stonith_t * stonith, int call_options, const char *id, char **list_info, int timeout) { int rc; xmlNode *output = NULL; rc = stonith_api_call(stonith, call_options, id, "list", NULL, timeout, &output); if (output && list_info) { const char *list_str; list_str = crm_element_value(output, F_STONITH_OUTPUT); if (list_str) { *list_info = strdup(list_str); } } if (output) { free_xml(output); } return rc; } static int stonith_api_monitor(stonith_t * stonith, int call_options, const char *id, int timeout) { return stonith_api_call(stonith, call_options, id, "monitor", NULL, timeout, NULL); } static int stonith_api_status(stonith_t * stonith, int call_options, const char *id, const char *port, int timeout) { return stonith_api_call(stonith, call_options, id, "status", port, timeout, NULL); } static int stonith_api_fence_with_delay(stonith_t * stonith, int call_options, const char *node, const char *action, int timeout, int tolerance, int delay) { int rc = 0; xmlNode *data = NULL; data = create_xml_node(NULL, __func__); crm_xml_add(data, F_STONITH_TARGET, node); crm_xml_add(data, F_STONITH_ACTION, action); crm_xml_add_int(data, F_STONITH_TIMEOUT, timeout); crm_xml_add_int(data, F_STONITH_TOLERANCE, tolerance); crm_xml_add_int(data, F_STONITH_DELAY, delay); rc = stonith_send_command(stonith, STONITH_OP_FENCE, data, NULL, call_options, timeout); free_xml(data); return rc; } static int stonith_api_fence(stonith_t * stonith, int call_options, const char *node, const char *action, int timeout, int tolerance) { return stonith_api_fence_with_delay(stonith, call_options, node, action, timeout, tolerance, 0); } static int stonith_api_confirm(stonith_t * stonith, int call_options, const char *target) { stonith__set_call_options(call_options, target, st_opt_manual_ack); return stonith_api_fence(stonith, call_options, target, "off", 0, 0); } static int stonith_api_history(stonith_t * stonith, int call_options, const char *node, stonith_history_t ** history, int timeout) { int rc = 0; xmlNode *data = NULL; xmlNode *output = NULL; stonith_history_t *last = NULL; *history = NULL; if (node) { data = create_xml_node(NULL, __func__); crm_xml_add(data, F_STONITH_TARGET, node); } stonith__set_call_options(call_options, node, st_opt_sync_call); rc = stonith_send_command(stonith, STONITH_OP_FENCE_HISTORY, data, &output, call_options, timeout); free_xml(data); if (rc == 0) { xmlNode *op = NULL; xmlNode *reply = get_xpath_object("//" F_STONITH_HISTORY_LIST, output, LOG_NEVER); for (op = pcmk__xml_first_child(reply); op != NULL; op = pcmk__xml_next(op)) { stonith_history_t *kvp; long long completed; long long completed_nsec = 0L; kvp = calloc(1, sizeof(stonith_history_t)); kvp->target = crm_element_value_copy(op, F_STONITH_TARGET); kvp->action = crm_element_value_copy(op, F_STONITH_ACTION); kvp->origin = crm_element_value_copy(op, F_STONITH_ORIGIN); kvp->delegate = crm_element_value_copy(op, F_STONITH_DELEGATE); kvp->client = crm_element_value_copy(op, F_STONITH_CLIENTNAME); crm_element_value_ll(op, F_STONITH_DATE, &completed); kvp->completed = (time_t) completed; crm_element_value_ll(op, F_STONITH_DATE_NSEC, &completed_nsec); kvp->completed_nsec = completed_nsec; crm_element_value_int(op, F_STONITH_STATE, &kvp->state); kvp->exit_reason = crm_element_value_copy(op, XML_LRM_ATTR_EXIT_REASON); if (last) { last->next = kvp; } else { *history = kvp; } last = kvp; } } free_xml(output); return rc; } void stonith_history_free(stonith_history_t *history) { stonith_history_t *hp, *hp_old; for (hp = history; hp; hp_old = hp, hp = hp->next, free(hp_old)) { free(hp->target); free(hp->action); free(hp->origin); free(hp->delegate); free(hp->client); free(hp->exit_reason); } } static gint stonithlib_GCompareFunc(gconstpointer a, gconstpointer b) { int rc = 0; const stonith_notify_client_t *a_client = a; const stonith_notify_client_t *b_client = b; if (a_client->delete || b_client->delete) { /* make entries marked for deletion not findable */ return -1; } CRM_CHECK(a_client->event != NULL && b_client->event != NULL, return 0); rc = strcmp(a_client->event, b_client->event); if (rc == 0) { if (a_client->notify == NULL || b_client->notify == NULL) { return 0; } else if (a_client->notify == b_client->notify) { return 0; } else if (((long)a_client->notify) < ((long)b_client->notify)) { crm_err("callbacks for %s are not equal: %p vs. %p", a_client->event, a_client->notify, b_client->notify); return -1; } crm_err("callbacks for %s are not equal: %p vs. %p", a_client->event, a_client->notify, b_client->notify); return 1; } return rc; } xmlNode * stonith_create_op(int call_id, const char *token, const char *op, xmlNode * data, int call_options) { xmlNode *op_msg = create_xml_node(NULL, "stonith_command"); CRM_CHECK(op_msg != NULL, return NULL); CRM_CHECK(token != NULL, return NULL); crm_xml_add(op_msg, F_XML_TAGNAME, "stonith_command"); crm_xml_add(op_msg, F_TYPE, T_STONITH_NG); crm_xml_add(op_msg, F_STONITH_CALLBACK_TOKEN, token); crm_xml_add(op_msg, F_STONITH_OPERATION, op); crm_xml_add_int(op_msg, F_STONITH_CALLID, call_id); crm_trace("Sending call options: %.8lx, %d", (long)call_options, call_options); crm_xml_add_int(op_msg, F_STONITH_CALLOPTS, call_options); if (data != NULL) { add_message_xml(op_msg, F_STONITH_CALLDATA, data); } return op_msg; } static void stonith_destroy_op_callback(gpointer data) { stonith_callback_client_t *blob = data; if (blob->timer && blob->timer->ref > 0) { g_source_remove(blob->timer->ref); } free(blob->timer); free(blob); } static int stonith_api_signoff(stonith_t * stonith) { stonith_private_t *native = stonith->st_private; crm_debug("Disconnecting from the fencer"); if (native->source != NULL) { /* Attached to mainloop */ mainloop_del_ipc_client(native->source); native->source = NULL; native->ipc = NULL; } else if (native->ipc) { /* Not attached to mainloop */ crm_ipc_t *ipc = native->ipc; native->ipc = NULL; crm_ipc_close(ipc); crm_ipc_destroy(ipc); } free(native->token); native->token = NULL; stonith->state = stonith_disconnected; return pcmk_ok; } static int stonith_api_del_callback(stonith_t * stonith, int call_id, bool all_callbacks) { stonith_private_t *private = stonith->st_private; if (all_callbacks) { private->op_callback = NULL; g_hash_table_destroy(private->stonith_op_callback_table); private->stonith_op_callback_table = pcmk__intkey_table(stonith_destroy_op_callback); } else if (call_id == 0) { private->op_callback = NULL; } else { pcmk__intkey_table_remove(private->stonith_op_callback_table, call_id); } return pcmk_ok; } /*! * \internal * \brief Invoke a (single) specified fence action callback * * \param[in] st Fencer API connection * \param[in] call_id If positive, call ID of completed fence action, otherwise * legacy return code for early action failure * \param[in] result Full result for action * \param[in] userdata User data to pass to callback * \param[in] callback Fence action callback to invoke */ static void invoke_fence_action_callback(stonith_t *st, int call_id, pcmk__action_result_t *result, void *userdata, void (*callback) (stonith_t *st, stonith_callback_data_t *data)) { stonith_callback_data_t data = { 0, }; data.call_id = call_id; data.rc = pcmk_rc2legacy(stonith__result2rc(result)); data.userdata = userdata; data.opaque = (void *) result; callback(st, &data); } /*! * \internal * \brief Invoke any callbacks registered for a specified fence action result * * Given a fence action result from the fencer, invoke any callback registered * for that action, as well as any global callback registered. * * \param[in] st Fencer API connection * \param[in] msg If non-NULL, fencer reply * \param[in] call_id If \p msg is NULL, call ID of action that timed out */ static void invoke_registered_callbacks(stonith_t *stonith, xmlNode *msg, int call_id) { stonith_private_t *private = NULL; stonith_callback_client_t *cb_info = NULL; pcmk__action_result_t result = PCMK__UNKNOWN_RESULT; CRM_CHECK(stonith != NULL, return); CRM_CHECK(stonith->st_private != NULL, return); private = stonith->st_private; if (msg == NULL) { // Fencer didn't reply in time pcmk__set_result(&result, CRM_EX_ERROR, PCMK_EXEC_TIMEOUT, "Fencer accepted request but did not reply in time"); CRM_LOG_ASSERT(call_id > 0); } else { // We have the fencer reply if ((crm_element_value_int(msg, F_STONITH_CALLID, &call_id) != 0) || (call_id <= 0)) { crm_log_xml_warn(msg, "Bad fencer reply"); } stonith__xe_get_result(msg, &result); } if (call_id > 0) { cb_info = pcmk__intkey_table_lookup(private->stonith_op_callback_table, call_id); } if ((cb_info != NULL) && (cb_info->callback != NULL) && (pcmk__result_ok(&result) || !(cb_info->only_success))) { crm_trace("Invoking callback %s for call %d", pcmk__s(cb_info->id, "without ID"), call_id); invoke_fence_action_callback(stonith, call_id, &result, cb_info->user_data, cb_info->callback); } else if ((private->op_callback == NULL) && !pcmk__result_ok(&result)) { crm_warn("Fencing action without registered callback failed: %d (%s%s%s)", result.exit_status, pcmk_exec_status_str(result.execution_status), ((result.exit_reason == NULL)? "" : ": "), ((result.exit_reason == NULL)? "" : result.exit_reason)); crm_log_xml_debug(msg, "Failed fence update"); } if (private->op_callback != NULL) { crm_trace("Invoking global callback for call %d", call_id); invoke_fence_action_callback(stonith, call_id, &result, NULL, private->op_callback); } if (cb_info != NULL) { stonith_api_del_callback(stonith, call_id, FALSE); } pcmk__reset_result(&result); } static gboolean stonith_async_timeout_handler(gpointer data) { struct timer_rec_s *timer = data; crm_err("Async call %d timed out after %dms", timer->call_id, timer->timeout); invoke_registered_callbacks(timer->stonith, NULL, timer->call_id); /* Always return TRUE, never remove the handler * We do that in stonith_del_callback() */ return TRUE; } static void set_callback_timeout(stonith_callback_client_t * callback, stonith_t * stonith, int call_id, int timeout) { struct timer_rec_s *async_timer = callback->timer; if (timeout <= 0) { return; } if (!async_timer) { async_timer = calloc(1, sizeof(struct timer_rec_s)); callback->timer = async_timer; } async_timer->stonith = stonith; async_timer->call_id = call_id; /* Allow a fair bit of grace to allow the server to tell us of a timeout * This is only a fallback */ async_timer->timeout = (timeout + 60) * 1000; if (async_timer->ref) { g_source_remove(async_timer->ref); } async_timer->ref = g_timeout_add(async_timer->timeout, stonith_async_timeout_handler, async_timer); } static void update_callback_timeout(int call_id, int timeout, stonith_t * st) { stonith_callback_client_t *callback = NULL; stonith_private_t *private = st->st_private; callback = pcmk__intkey_table_lookup(private->stonith_op_callback_table, call_id); if (!callback || !callback->allow_timeout_updates) { return; } set_callback_timeout(callback, st, call_id, timeout); } static int stonith_dispatch_internal(const char *buffer, ssize_t length, gpointer userdata) { const char *type = NULL; struct notify_blob_s blob; stonith_t *st = userdata; stonith_private_t *private = NULL; CRM_ASSERT(st != NULL); private = st->st_private; blob.stonith = st; blob.xml = string2xml(buffer); if (blob.xml == NULL) { crm_warn("Received malformed message from fencer: %s", buffer); return 0; } /* do callbacks */ type = crm_element_value(blob.xml, F_TYPE); crm_trace("Activating %s callbacks...", type); if (pcmk__str_eq(type, T_STONITH_NG, pcmk__str_none)) { invoke_registered_callbacks(st, blob.xml, 0); } else if (pcmk__str_eq(type, T_STONITH_NOTIFY, pcmk__str_none)) { foreach_notify_entry(private, stonith_send_notification, &blob); } else if (pcmk__str_eq(type, T_STONITH_TIMEOUT_VALUE, pcmk__str_none)) { int call_id = 0; int timeout = 0; crm_element_value_int(blob.xml, F_STONITH_TIMEOUT, &timeout); crm_element_value_int(blob.xml, F_STONITH_CALLID, &call_id); update_callback_timeout(call_id, timeout, st); } else { crm_err("Unknown message type: %s", type); crm_log_xml_warn(blob.xml, "BadReply"); } free_xml(blob.xml); return 1; } static int stonith_api_signon(stonith_t * stonith, const char *name, int *stonith_fd) { int rc = pcmk_ok; stonith_private_t *native = NULL; const char *display_name = name? name : "client"; struct ipc_client_callbacks st_callbacks = { .dispatch = stonith_dispatch_internal, .destroy = stonith_connection_destroy }; CRM_CHECK(stonith != NULL, return -EINVAL); native = stonith->st_private; CRM_ASSERT(native != NULL); crm_debug("Attempting fencer connection by %s with%s mainloop", display_name, (stonith_fd? "out" : "")); stonith->state = stonith_connected_command; if (stonith_fd) { /* No mainloop */ native->ipc = crm_ipc_new("stonith-ng", 0); if (native->ipc && crm_ipc_connect(native->ipc)) { *stonith_fd = crm_ipc_get_fd(native->ipc); } else if (native->ipc) { crm_ipc_close(native->ipc); crm_ipc_destroy(native->ipc); native->ipc = NULL; } } else { /* With mainloop */ native->source = mainloop_add_ipc_client("stonith-ng", G_PRIORITY_MEDIUM, 0, stonith, &st_callbacks); native->ipc = mainloop_get_ipc_client(native->source); } if (native->ipc == NULL) { rc = -ENOTCONN; } else { xmlNode *reply = NULL; xmlNode *hello = create_xml_node(NULL, "stonith_command"); crm_xml_add(hello, F_TYPE, T_STONITH_NG); crm_xml_add(hello, F_STONITH_OPERATION, CRM_OP_REGISTER); crm_xml_add(hello, F_STONITH_CLIENTNAME, name); rc = crm_ipc_send(native->ipc, hello, crm_ipc_client_response, -1, &reply); if (rc < 0) { crm_debug("Couldn't register with the fencer: %s " CRM_XS " rc=%d", pcmk_strerror(rc), rc); rc = -ECOMM; } else if (reply == NULL) { crm_debug("Couldn't register with the fencer: no reply"); rc = -EPROTO; } else { const char *msg_type = crm_element_value(reply, F_STONITH_OPERATION); native->token = crm_element_value_copy(reply, F_STONITH_CLIENTID); if (!pcmk__str_eq(msg_type, CRM_OP_REGISTER, pcmk__str_none)) { crm_debug("Couldn't register with the fencer: invalid reply type '%s'", (msg_type? msg_type : "(missing)")); crm_log_xml_debug(reply, "Invalid fencer reply"); rc = -EPROTO; } else if (native->token == NULL) { crm_debug("Couldn't register with the fencer: no token in reply"); crm_log_xml_debug(reply, "Invalid fencer reply"); rc = -EPROTO; } else { crm_debug("Connection to fencer by %s succeeded (registration token: %s)", display_name, native->token); rc = pcmk_ok; } } free_xml(reply); free_xml(hello); } if (rc != pcmk_ok) { crm_debug("Connection attempt to fencer by %s failed: %s " CRM_XS " rc=%d", display_name, pcmk_strerror(rc), rc); stonith->cmds->disconnect(stonith); } return rc; } static int stonith_set_notification(stonith_t * stonith, const char *callback, int enabled) { int rc = pcmk_ok; xmlNode *notify_msg = create_xml_node(NULL, __func__); stonith_private_t *native = stonith->st_private; if (stonith->state != stonith_disconnected) { crm_xml_add(notify_msg, F_STONITH_OPERATION, T_STONITH_NOTIFY); if (enabled) { crm_xml_add(notify_msg, F_STONITH_NOTIFY_ACTIVATE, callback); } else { crm_xml_add(notify_msg, F_STONITH_NOTIFY_DEACTIVATE, callback); } rc = crm_ipc_send(native->ipc, notify_msg, crm_ipc_client_response, -1, NULL); if (rc < 0) { crm_perror(LOG_DEBUG, "Couldn't register for fencing notifications: %d", rc); rc = -ECOMM; } else { rc = pcmk_ok; } } free_xml(notify_msg); return rc; } static int stonith_api_add_notification(stonith_t * stonith, const char *event, void (*callback) (stonith_t * stonith, stonith_event_t * e)) { GList *list_item = NULL; stonith_notify_client_t *new_client = NULL; stonith_private_t *private = NULL; private = stonith->st_private; crm_trace("Adding callback for %s events (%d)", event, g_list_length(private->notify_list)); new_client = calloc(1, sizeof(stonith_notify_client_t)); new_client->event = event; new_client->notify = callback; list_item = g_list_find_custom(private->notify_list, new_client, stonithlib_GCompareFunc); if (list_item != NULL) { crm_warn("Callback already present"); free(new_client); return -ENOTUNIQ; } else { private->notify_list = g_list_append(private->notify_list, new_client); stonith_set_notification(stonith, event, 1); crm_trace("Callback added (%d)", g_list_length(private->notify_list)); } return pcmk_ok; } static void del_notify_entry(gpointer data, gpointer user_data) { stonith_notify_client_t *entry = data; stonith_t * stonith = user_data; if (!entry->delete) { crm_debug("Removing callback for %s events", entry->event); stonith_api_del_notification(stonith, entry->event); } } static int stonith_api_del_notification(stonith_t * stonith, const char *event) { GList *list_item = NULL; stonith_notify_client_t *new_client = NULL; stonith_private_t *private = stonith->st_private; if (event == NULL) { foreach_notify_entry(private, del_notify_entry, stonith); crm_trace("Removed callback"); return pcmk_ok; } crm_debug("Removing callback for %s events", event); new_client = calloc(1, sizeof(stonith_notify_client_t)); new_client->event = event; new_client->notify = NULL; list_item = g_list_find_custom(private->notify_list, new_client, stonithlib_GCompareFunc); stonith_set_notification(stonith, event, 0); if (list_item != NULL) { stonith_notify_client_t *list_client = list_item->data; if (private->notify_refcnt) { list_client->delete = TRUE; private->notify_deletes = TRUE; } else { private->notify_list = g_list_remove(private->notify_list, list_client); free(list_client); } crm_trace("Removed callback"); } else { crm_trace("Callback not present"); } free(new_client); return pcmk_ok; } static int stonith_api_add_callback(stonith_t * stonith, int call_id, int timeout, int options, void *user_data, const char *callback_name, void (*callback) (stonith_t * st, stonith_callback_data_t * data)) { stonith_callback_client_t *blob = NULL; stonith_private_t *private = NULL; CRM_CHECK(stonith != NULL, return -EINVAL); CRM_CHECK(stonith->st_private != NULL, return -EINVAL); private = stonith->st_private; if (call_id == 0) { // Add global callback private->op_callback = callback; } else if (call_id < 0) { // Call failed immediately, so call callback now if (!(options & st_opt_report_only_success)) { pcmk__action_result_t result = PCMK__UNKNOWN_RESULT; crm_trace("Call failed, calling %s: %s", callback_name, pcmk_strerror(call_id)); pcmk__set_result(&result, CRM_EX_ERROR, stonith__legacy2status(call_id), NULL); invoke_fence_action_callback(stonith, call_id, &result, user_data, callback); } else { crm_warn("Fencer call failed: %s", pcmk_strerror(call_id)); } return FALSE; } blob = calloc(1, sizeof(stonith_callback_client_t)); blob->id = callback_name; blob->only_success = (options & st_opt_report_only_success) ? TRUE : FALSE; blob->user_data = user_data; blob->callback = callback; blob->allow_timeout_updates = (options & st_opt_timeout_updates) ? TRUE : FALSE; if (timeout > 0) { set_callback_timeout(blob, stonith, call_id, timeout); } pcmk__intkey_table_insert(private->stonith_op_callback_table, call_id, blob); crm_trace("Added callback to %s for call %d", callback_name, call_id); return TRUE; } static void stonith_dump_pending_op(gpointer key, gpointer value, gpointer user_data) { int call = GPOINTER_TO_INT(key); stonith_callback_client_t *blob = value; crm_debug("Call %d (%s): pending", call, pcmk__s(blob->id, "no ID")); } void stonith_dump_pending_callbacks(stonith_t * stonith) { stonith_private_t *private = stonith->st_private; if (private->stonith_op_callback_table == NULL) { return; } return g_hash_table_foreach(private->stonith_op_callback_table, stonith_dump_pending_op, NULL); } /*! * \internal * \brief Get the data section of a fencer notification * * \param[in] msg Notification XML * \param[in] ntype Notification type */ static xmlNode * get_event_data_xml(xmlNode *msg, const char *ntype) { char *data_addr = crm_strdup_printf("//%s", ntype); xmlNode *data = get_xpath_object(data_addr, msg, LOG_DEBUG); free(data_addr); return data; } /* */ static stonith_event_t * xml_to_event(xmlNode *msg) { stonith_event_t *event = calloc(1, sizeof(stonith_event_t)); struct event_private *event_private = NULL; CRM_ASSERT(event != NULL); event->opaque = calloc(1, sizeof(struct event_private)); CRM_ASSERT(event->opaque != NULL); event_private = (struct event_private *) event->opaque; crm_log_xml_trace(msg, "stonith_notify"); // All notification types have the operation result and notification subtype stonith__xe_get_result(msg, &event_private->result); event->operation = crm_element_value_copy(msg, F_STONITH_OPERATION); // @COMPAT The API originally provided the result as a legacy return code event->result = pcmk_rc2legacy(stonith__result2rc(&event_private->result)); // Some notification subtypes have additional information if (pcmk__str_eq(event->operation, T_STONITH_NOTIFY_FENCE, pcmk__str_none)) { xmlNode *data = get_event_data_xml(msg, event->operation); if (data == NULL) { crm_err("No data for %s event", event->operation); crm_log_xml_notice(msg, "BadEvent"); } else { event->origin = crm_element_value_copy(data, F_STONITH_ORIGIN); event->action = crm_element_value_copy(data, F_STONITH_ACTION); event->target = crm_element_value_copy(data, F_STONITH_TARGET); event->executioner = crm_element_value_copy(data, F_STONITH_DELEGATE); event->id = crm_element_value_copy(data, F_STONITH_REMOTE_OP_ID); event->client_origin = crm_element_value_copy(data, F_STONITH_CLIENTNAME); event->device = crm_element_value_copy(data, F_STONITH_DEVICE); } } else if (pcmk__str_any_of(event->operation, STONITH_OP_DEVICE_ADD, STONITH_OP_DEVICE_DEL, STONITH_OP_LEVEL_ADD, STONITH_OP_LEVEL_DEL, NULL)) { xmlNode *data = get_event_data_xml(msg, event->operation); if (data == NULL) { crm_err("No data for %s event", event->operation); crm_log_xml_notice(msg, "BadEvent"); } else { event->device = crm_element_value_copy(data, F_STONITH_DEVICE); } } return event; } static void event_free(stonith_event_t * event) { struct event_private *event_private = event->opaque; free(event->id); free(event->type); free(event->message); free(event->operation); free(event->origin); free(event->action); free(event->target); free(event->executioner); free(event->device); free(event->client_origin); pcmk__reset_result(&event_private->result); free(event->opaque); free(event); } static void stonith_send_notification(gpointer data, gpointer user_data) { struct notify_blob_s *blob = user_data; stonith_notify_client_t *entry = data; stonith_event_t *st_event = NULL; const char *event = NULL; if (blob->xml == NULL) { crm_warn("Skipping callback - NULL message"); return; } event = crm_element_value(blob->xml, F_SUBTYPE); if (entry == NULL) { crm_warn("Skipping callback - NULL callback client"); return; } else if (entry->delete) { crm_trace("Skipping callback - marked for deletion"); return; } else if (entry->notify == NULL) { crm_warn("Skipping callback - NULL callback"); return; } else if (!pcmk__str_eq(entry->event, event, pcmk__str_none)) { crm_trace("Skipping callback - event mismatch %p/%s vs. %s", entry, entry->event, event); return; } st_event = xml_to_event(blob->xml); crm_trace("Invoking callback for %p/%s event...", entry, event); entry->notify(blob->stonith, st_event); crm_trace("Callback invoked..."); event_free(st_event); } /*! * \internal * \brief Create and send an API request * * \param[in] stonith Stonith connection * \param[in] op API operation to request * \param[in] data Data to attach to request * \param[out] output_data If not NULL, will be set to reply if synchronous * \param[in] call_options Bitmask of stonith_call_options to use * \param[in] timeout Error if not completed within this many seconds * * \return pcmk_ok (for synchronous requests) or positive call ID * (for asynchronous requests) on success, -errno otherwise */ static int stonith_send_command(stonith_t * stonith, const char *op, xmlNode * data, xmlNode ** output_data, int call_options, int timeout) { int rc = 0; int reply_id = -1; xmlNode *op_msg = NULL; xmlNode *op_reply = NULL; stonith_private_t *native = NULL; CRM_ASSERT(stonith && stonith->st_private && op); native = stonith->st_private; if (output_data != NULL) { *output_data = NULL; } if ((stonith->state == stonith_disconnected) || (native->token == NULL)) { return -ENOTCONN; } /* Increment the call ID, which must be positive to avoid conflicting with * error codes. This shouldn't be a problem unless the client mucked with * it or the counter wrapped around. */ stonith->call_id++; if (stonith->call_id < 1) { stonith->call_id = 1; } op_msg = stonith_create_op(stonith->call_id, native->token, op, data, call_options); if (op_msg == NULL) { return -EINVAL; } crm_xml_add_int(op_msg, F_STONITH_TIMEOUT, timeout); crm_trace("Sending %s message to fencer with timeout %ds", op, timeout); if (data) { const char *delay_s = crm_element_value(data, F_STONITH_DELAY); if (delay_s) { crm_xml_add(op_msg, F_STONITH_DELAY, delay_s); } } { enum crm_ipc_flags ipc_flags = crm_ipc_flags_none; if (call_options & st_opt_sync_call) { pcmk__set_ipc_flags(ipc_flags, "stonith command", crm_ipc_client_response); } rc = crm_ipc_send(native->ipc, op_msg, ipc_flags, 1000 * (timeout + 60), &op_reply); } free_xml(op_msg); if (rc < 0) { crm_perror(LOG_ERR, "Couldn't perform %s operation (timeout=%ds): %d", op, timeout, rc); rc = -ECOMM; goto done; } crm_log_xml_trace(op_reply, "Reply"); if (!(call_options & st_opt_sync_call)) { crm_trace("Async call %d, returning", stonith->call_id); free_xml(op_reply); return stonith->call_id; } rc = pcmk_ok; crm_element_value_int(op_reply, F_STONITH_CALLID, &reply_id); if (reply_id == stonith->call_id) { pcmk__action_result_t result = PCMK__UNKNOWN_RESULT; crm_trace("Synchronous reply %d received", reply_id); stonith__xe_get_result(op_reply, &result); rc = pcmk_rc2legacy(stonith__result2rc(&result)); pcmk__reset_result(&result); if ((call_options & st_opt_discard_reply) || output_data == NULL) { crm_trace("Discarding reply"); } else { *output_data = op_reply; op_reply = NULL; /* Prevent subsequent free */ } } else if (reply_id <= 0) { crm_err("Received bad reply: No id set"); crm_log_xml_err(op_reply, "Bad reply"); free_xml(op_reply); rc = -ENOMSG; } else { crm_err("Received bad reply: %d (wanted %d)", reply_id, stonith->call_id); crm_log_xml_err(op_reply, "Old reply"); free_xml(op_reply); rc = -ENOMSG; } done: if (crm_ipc_connected(native->ipc) == FALSE) { crm_err("Fencer disconnected"); free(native->token); native->token = NULL; stonith->state = stonith_disconnected; } free_xml(op_reply); return rc; } /* Not used with mainloop */ bool stonith_dispatch(stonith_t * st) { gboolean stay_connected = TRUE; stonith_private_t *private = NULL; CRM_ASSERT(st != NULL); private = st->st_private; while (crm_ipc_ready(private->ipc)) { if (crm_ipc_read(private->ipc) > 0) { const char *msg = crm_ipc_buffer(private->ipc); stonith_dispatch_internal(msg, strlen(msg), st); } if (crm_ipc_connected(private->ipc) == FALSE) { crm_err("Connection closed"); stay_connected = FALSE; } } return stay_connected; } static int stonith_api_free(stonith_t * stonith) { int rc = pcmk_ok; crm_trace("Destroying %p", stonith); if (stonith->state != stonith_disconnected) { crm_trace("Disconnecting %p first", stonith); rc = stonith->cmds->disconnect(stonith); } if (stonith->state == stonith_disconnected) { stonith_private_t *private = stonith->st_private; crm_trace("Removing %d callbacks", g_hash_table_size(private->stonith_op_callback_table)); g_hash_table_destroy(private->stonith_op_callback_table); crm_trace("Destroying %d notification clients", g_list_length(private->notify_list)); g_list_free_full(private->notify_list, free); free(stonith->st_private); free(stonith->cmds); free(stonith); } else { crm_err("Not free'ing active connection: %s (%d)", pcmk_strerror(rc), rc); } return rc; } void stonith_api_delete(stonith_t * stonith) { crm_trace("Destroying %p", stonith); if(stonith) { stonith->cmds->free(stonith); } } static int stonith_api_validate(stonith_t *st, int call_options, const char *rsc_id, const char *namespace_s, const char *agent, - stonith_key_value_t *params, int timeout_sec, + const stonith_key_value_t *params, int timeout_sec, char **output, char **error_output) { /* Validation should be done directly via the agent, so we can get it from * stonith_admin when the cluster is not running, which is important for * higher-level tools. */ int rc = pcmk_ok; /* Use a dummy node name in case the agent requires a target. We assume the * actual target doesn't matter for validation purposes (if in practice, * that is incorrect, we will need to allow the caller to pass the target). */ const char *target = "node1"; const char *host_arg = NULL; GHashTable *params_table = pcmk__strkey_table(free, free); // Convert parameter list to a hash table for (; params; params = params->next) { if (pcmk__str_eq(params->key, PCMK_STONITH_HOST_ARGUMENT, pcmk__str_none)) { host_arg = params->value; } if (!pcmk_stonith_param(params->key)) { g_hash_table_insert(params_table, strdup(params->key), strdup(params->value)); } } #if SUPPORT_CIBSECRETS rc = pcmk__substitute_secrets(rsc_id, params_table); if (rc != pcmk_rc_ok) { crm_warn("Could not replace secret parameters for validation of %s: %s", agent, pcmk_rc_str(rc)); // rc is standard return value, don't return it in this function } #endif if (output) { *output = NULL; } if (error_output) { *error_output = NULL; } if (timeout_sec <= 0) { timeout_sec = CRMD_METADATA_CALL_TIMEOUT; // Questionable } switch (stonith_get_namespace(agent, namespace_s)) { case st_namespace_rhcs: rc = stonith__rhcs_validate(st, call_options, target, agent, params_table, host_arg, timeout_sec, output, error_output); break; #if HAVE_STONITH_STONITH_H case st_namespace_lha: rc = stonith__lha_validate(st, call_options, target, agent, params_table, timeout_sec, output, error_output); break; #endif case st_namespace_invalid: errno = ENOENT; rc = -errno; if (error_output) { *error_output = crm_strdup_printf("Agent %s not found", agent); } else { crm_err("Agent %s not found", agent); } break; default: errno = EOPNOTSUPP; rc = -errno; if (error_output) { *error_output = crm_strdup_printf("Agent %s does not support validation", agent); } else { crm_err("Agent %s does not support validation", agent); } break; } g_hash_table_destroy(params_table); return rc; } stonith_t * stonith_api_new(void) { stonith_t *new_stonith = NULL; stonith_private_t *private = NULL; new_stonith = calloc(1, sizeof(stonith_t)); if (new_stonith == NULL) { return NULL; } private = calloc(1, sizeof(stonith_private_t)); if (private == NULL) { free(new_stonith); return NULL; } new_stonith->st_private = private; private->stonith_op_callback_table = pcmk__intkey_table(stonith_destroy_op_callback); private->notify_list = NULL; private->notify_refcnt = 0; private->notify_deletes = FALSE; new_stonith->call_id = 1; new_stonith->state = stonith_disconnected; new_stonith->cmds = calloc(1, sizeof(stonith_api_operations_t)); if (new_stonith->cmds == NULL) { free(new_stonith->st_private); free(new_stonith); return NULL; } /* *INDENT-OFF* */ new_stonith->cmds->free = stonith_api_free; new_stonith->cmds->connect = stonith_api_signon; new_stonith->cmds->disconnect = stonith_api_signoff; new_stonith->cmds->list = stonith_api_list; new_stonith->cmds->monitor = stonith_api_monitor; new_stonith->cmds->status = stonith_api_status; new_stonith->cmds->fence = stonith_api_fence; new_stonith->cmds->fence_with_delay = stonith_api_fence_with_delay; new_stonith->cmds->confirm = stonith_api_confirm; new_stonith->cmds->history = stonith_api_history; new_stonith->cmds->list_agents = stonith_api_device_list; new_stonith->cmds->metadata = stonith_api_device_metadata; new_stonith->cmds->query = stonith_api_query; new_stonith->cmds->remove_device = stonith_api_remove_device; new_stonith->cmds->register_device = stonith_api_register_device; new_stonith->cmds->remove_level = stonith_api_remove_level; new_stonith->cmds->remove_level_full = stonith_api_remove_level_full; new_stonith->cmds->register_level = stonith_api_register_level; new_stonith->cmds->register_level_full = stonith_api_register_level_full; new_stonith->cmds->remove_callback = stonith_api_del_callback; new_stonith->cmds->register_callback = stonith_api_add_callback; new_stonith->cmds->remove_notification = stonith_api_del_notification; new_stonith->cmds->register_notification = stonith_api_add_notification; new_stonith->cmds->validate = stonith_api_validate; /* *INDENT-ON* */ return new_stonith; } /*! * \brief Make a blocking connection attempt to the fencer * * \param[in,out] st Fencer API object * \param[in] name Client name to use with fencer * \param[in] max_attempts Return error if this many attempts fail * * \return pcmk_ok on success, result of last attempt otherwise */ int stonith_api_connect_retry(stonith_t *st, const char *name, int max_attempts) { int rc = -EINVAL; // if max_attempts is not positive for (int attempt = 1; attempt <= max_attempts; attempt++) { rc = st->cmds->connect(st, name, NULL); if (rc == pcmk_ok) { return pcmk_ok; } else if (attempt < max_attempts) { crm_notice("Fencer connection attempt %d of %d failed (retrying in 2s): %s " CRM_XS " rc=%d", attempt, max_attempts, pcmk_strerror(rc), rc); sleep(2); } } crm_notice("Could not connect to fencer: %s " CRM_XS " rc=%d", pcmk_strerror(rc), rc); return rc; } stonith_key_value_t * stonith_key_value_add(stonith_key_value_t * head, const char *key, const char *value) { stonith_key_value_t *p, *end; p = calloc(1, sizeof(stonith_key_value_t)); pcmk__str_update(&p->key, key); pcmk__str_update(&p->value, value); end = head; while (end && end->next) { end = end->next; } if (end) { end->next = p; } else { head = p; } return head; } void stonith_key_value_freeall(stonith_key_value_t * head, int keys, int values) { stonith_key_value_t *p; while (head) { p = head->next; if (keys) { free(head->key); } if (values) { free(head->value); } free(head); head = p; } } #define api_log_open() openlog("stonith-api", LOG_CONS | LOG_NDELAY | LOG_PID, LOG_DAEMON) #define api_log(level, fmt, args...) syslog(level, "%s: "fmt, __func__, args) int stonith_api_kick(uint32_t nodeid, const char *uname, int timeout, bool off) { int rc = pcmk_ok; stonith_t *st = stonith_api_new(); const char *action = off? "off" : "reboot"; api_log_open(); if (st == NULL) { api_log(LOG_ERR, "API initialization failed, could not kick (%s) node %u/%s", action, nodeid, uname); return -EPROTO; } rc = st->cmds->connect(st, "stonith-api", NULL); if (rc != pcmk_ok) { api_log(LOG_ERR, "Connection failed, could not kick (%s) node %u/%s : %s (%d)", action, nodeid, uname, pcmk_strerror(rc), rc); } else { char *name = (uname == NULL)? pcmk__itoa(nodeid) : strdup(uname); int opts = 0; stonith__set_call_options(opts, name, st_opt_sync_call|st_opt_allow_suicide); if ((uname == NULL) && (nodeid > 0)) { stonith__set_call_options(opts, name, st_opt_cs_nodeid); } rc = st->cmds->fence(st, opts, name, action, timeout, 0); free(name); if (rc != pcmk_ok) { api_log(LOG_ERR, "Could not kick (%s) node %u/%s : %s (%d)", action, nodeid, uname, pcmk_strerror(rc), rc); } else { api_log(LOG_NOTICE, "Node %u/%s kicked: %s", nodeid, uname, action); } } stonith_api_delete(st); return rc; } time_t stonith_api_time(uint32_t nodeid, const char *uname, bool in_progress) { int rc = pcmk_ok; time_t when = 0; stonith_t *st = stonith_api_new(); stonith_history_t *history = NULL, *hp = NULL; if (st == NULL) { api_log(LOG_ERR, "Could not retrieve fence history for %u/%s: " "API initialization failed", nodeid, uname); return when; } rc = st->cmds->connect(st, "stonith-api", NULL); if (rc != pcmk_ok) { api_log(LOG_NOTICE, "Connection failed: %s (%d)", pcmk_strerror(rc), rc); } else { int entries = 0; int progress = 0; int completed = 0; int opts = 0; char *name = (uname == NULL)? pcmk__itoa(nodeid) : strdup(uname); stonith__set_call_options(opts, name, st_opt_sync_call); if ((uname == NULL) && (nodeid > 0)) { stonith__set_call_options(opts, name, st_opt_cs_nodeid); } rc = st->cmds->history(st, opts, name, &history, 120); free(name); for (hp = history; hp; hp = hp->next) { entries++; if (in_progress) { progress++; if (hp->state != st_done && hp->state != st_failed) { when = time(NULL); } } else if (hp->state == st_done) { completed++; if (hp->completed > when) { when = hp->completed; } } } stonith_history_free(history); if(rc == pcmk_ok) { api_log(LOG_INFO, "Found %d entries for %u/%s: %d in progress, %d completed", entries, nodeid, uname, progress, completed); } else { api_log(LOG_ERR, "Could not retrieve fence history for %u/%s: %s (%d)", nodeid, uname, pcmk_strerror(rc), rc); } } stonith_api_delete(st); if(when) { api_log(LOG_INFO, "Node %u/%s last kicked at: %ld", nodeid, uname, (long int)when); } return when; } bool stonith_agent_exists(const char *agent, int timeout) { stonith_t *st = NULL; stonith_key_value_t *devices = NULL; stonith_key_value_t *dIter = NULL; bool rc = FALSE; if (agent == NULL) { return rc; } st = stonith_api_new(); if (st == NULL) { crm_err("Could not list fence agents: API memory allocation failed"); return FALSE; } st->cmds->list_agents(st, st_opt_sync_call, NULL, &devices, timeout == 0 ? 120 : timeout); for (dIter = devices; dIter != NULL; dIter = dIter->next) { if (pcmk__str_eq(dIter->value, agent, pcmk__str_none)) { rc = TRUE; break; } } stonith_key_value_freeall(devices, 1, 1); stonith_api_delete(st); return rc; } const char * stonith_action_str(const char *action) { if (action == NULL) { return "fencing"; } else if (!strcmp(action, "on")) { return "unfencing"; } else if (!strcmp(action, "off")) { return "turning off"; } else { return action; } } /*! * \internal * \brief Parse a target name from one line of a target list string * * \param[in] line One line of a target list string * \param[in] len String length of line * \param[in,out] output List to add newly allocated target name to */ static void parse_list_line(const char *line, int len, GList **output) { size_t i = 0; size_t entry_start = 0; /* Skip complaints about additional parameters device doesn't understand * * @TODO Document or eliminate the implied restriction of target names */ if (strstr(line, "invalid") || strstr(line, "variable")) { crm_debug("Skipping list output line: %s", line); return; } // Process line content, character by character for (i = 0; i <= len; i++) { if (isspace(line[i]) || (line[i] == ',') || (line[i] == ';') || (line[i] == '\0')) { // We've found a separator (i.e. the end of an entry) int rc = 0; char *entry = NULL; if (i == entry_start) { // Skip leading and sequential separators entry_start = i + 1; continue; } entry = calloc(i - entry_start + 1, sizeof(char)); CRM_ASSERT(entry != NULL); /* Read entry, stopping at first separator * * @TODO Document or eliminate these character restrictions */ rc = sscanf(line + entry_start, "%[a-zA-Z0-9_-.]", entry); if (rc != 1) { crm_warn("Could not parse list output entry: %s " CRM_XS " entry_start=%d position=%d", line + entry_start, entry_start, i); free(entry); } else if (pcmk__strcase_any_of(entry, "on", "off", NULL)) { /* Some agents print the target status in the list output, * though none are known now (the separate list-status command * is used for this, but it can also print "UNKNOWN"). To handle * this possibility, skip such entries. * * @TODO Document or eliminate the implied restriction of target * names. */ free(entry); } else { // We have a valid entry *output = g_list_append(*output, entry); } entry_start = i + 1; } } } /*! * \internal * \brief Parse a list of targets from a string * * \param[in] list_output Target list as a string * * \return List of target names * \note The target list string format is flexible, to allow for user-specified * lists such pcmk_host_list and the output of an agent's list action * (whether direct or via the API, which escapes newlines). There may be * multiple lines, separated by either a newline or an escaped newline * (backslash n). Each line may have one or more target names, separated * by any combination of whitespace, commas, and semi-colons. Lines * containing "invalid" or "variable" will be ignored entirely. Target * names "on" or "off" (case-insensitive) will be ignored. Target names * may contain only alphanumeric characters, underbars (_), dashes (-), * and dots (.) (if any other character occurs in the name, it and all * subsequent characters in the name will be ignored). * \note The caller is responsible for freeing the result with * g_list_free_full(result, free). */ GList * stonith__parse_targets(const char *target_spec) { GList *targets = NULL; if (target_spec != NULL) { size_t out_len = strlen(target_spec); size_t line_start = 0; // Starting index of line being processed for (size_t i = 0; i <= out_len; ++i) { if ((target_spec[i] == '\n') || (target_spec[i] == '\0') || ((target_spec[i] == '\\') && (target_spec[i + 1] == 'n'))) { // We've reached the end of one line of output int len = i - line_start; if (len > 0) { char *line = strndup(target_spec + line_start, len); line[len] = '\0'; // Because it might be a newline parse_list_line(line, len, &targets); free(line); } if (target_spec[i] == '\\') { ++i; // backslash-n takes up two positions } line_start = i + 1; } } } return targets; } /*! * \internal * \brief Check whether a fencing failure was followed by an equivalent success * * \param[in] event Fencing failure * \param[in] top_history Complete fencing history (must be sorted by * stonith__sort_history() beforehand) * * \return The name of the node that executed the fencing if a later successful * event exists, or NULL if no such event exists */ const char * stonith__later_succeeded(stonith_history_t *event, stonith_history_t *top_history) { const char *other = NULL; for (stonith_history_t *prev_hp = top_history; prev_hp; prev_hp = prev_hp->next) { if (prev_hp == event) { break; } if ((prev_hp->state == st_done) && pcmk__str_eq(event->target, prev_hp->target, pcmk__str_casei) && pcmk__str_eq(event->action, prev_hp->action, pcmk__str_none) && ((event->completed < prev_hp->completed) || ((event->completed == prev_hp->completed) && (event->completed_nsec < prev_hp->completed_nsec)))) { if ((event->delegate == NULL) || pcmk__str_eq(event->delegate, prev_hp->delegate, pcmk__str_casei)) { // Prefer equivalent fencing by same executioner return prev_hp->delegate; } else if (other == NULL) { // Otherwise remember first successful executioner other = (prev_hp->delegate == NULL)? "some node" : prev_hp->delegate; } } } return other; } /*! * \internal * \brief Sort the stonith-history * sort by competed most current on the top * pending actions lacking a completed-stamp are gathered at the top * * \param[in] history List of stonith actions * */ stonith_history_t * stonith__sort_history(stonith_history_t *history) { stonith_history_t *new = NULL, *pending = NULL, *hp, *np, *tmp; for (hp = history; hp; ) { tmp = hp->next; if ((hp->state == st_done) || (hp->state == st_failed)) { /* sort into new */ if ((!new) || (hp->completed > new->completed) || ((hp->completed == new->completed) && (hp->completed_nsec > new->completed_nsec))) { hp->next = new; new = hp; } else { np = new; do { if ((!np->next) || (hp->completed > np->next->completed) || ((hp->completed == np->next->completed) && (hp->completed_nsec > np->next->completed_nsec))) { hp->next = np->next; np->next = hp; break; } np = np->next; } while (1); } } else { /* put into pending */ hp->next = pending; pending = hp; } hp = tmp; } /* pending actions don't have a completed-stamp so make them go front */ if (pending) { stonith_history_t *last_pending = pending; while (last_pending->next) { last_pending = last_pending->next; } last_pending->next = new; new = pending; } return new; } /*! * \brief Return string equivalent of an operation state value * * \param[in] state Fencing operation state value * * \return Human-friendly string equivalent of state */ const char * stonith_op_state_str(enum op_state state) { switch (state) { case st_query: return "querying"; case st_exec: return "executing"; case st_done: return "completed"; case st_duplicate: return "duplicate"; case st_failed: return "failed"; } return "unknown"; } stonith_history_t * stonith__first_matching_event(stonith_history_t *history, bool (*matching_fn)(stonith_history_t *, void *), void *user_data) { for (stonith_history_t *hp = history; hp; hp = hp->next) { if (matching_fn(hp, user_data)) { return hp; } } return NULL; } bool stonith__event_state_pending(stonith_history_t *history, void *user_data) { return history->state != st_failed && history->state != st_done; } bool stonith__event_state_eq(stonith_history_t *history, void *user_data) { return history->state == GPOINTER_TO_INT(user_data); } bool stonith__event_state_neq(stonith_history_t *history, void *user_data) { return history->state != GPOINTER_TO_INT(user_data); } void stonith__device_parameter_flags(uint32_t *device_flags, const char *device_name, xmlNode *metadata) { xmlXPathObjectPtr xpath = NULL; int max = 0; int lpc = 0; CRM_CHECK((device_flags != NULL) && (metadata != NULL), return); xpath = xpath_search(metadata, "//parameter"); max = numXpathResults(xpath); if (max <= 0) { freeXpathObject(xpath); return; } for (lpc = 0; lpc < max; lpc++) { const char *parameter = NULL; xmlNode *match = getXpathResult(xpath, lpc); CRM_LOG_ASSERT(match != NULL); if (match == NULL) { continue; } parameter = crm_element_value(match, "name"); if (pcmk__str_eq(parameter, "plug", pcmk__str_casei)) { stonith__set_device_flags(*device_flags, device_name, st_device_supports_parameter_plug); } else if (pcmk__str_eq(parameter, "port", pcmk__str_casei)) { stonith__set_device_flags(*device_flags, device_name, st_device_supports_parameter_port); } } freeXpathObject(xpath); } /*! * \internal * \brief Retrieve fence agent meta-data asynchronously * * \param[in] agent Agent to execute * \param[in] timeout_sec Error if not complete within this time * \param[in] callback Function to call with result (this will always be * called, whether by this function directly or later * via the main loop, and on success the metadata will * be in its result argument's action_stdout) * \param[in] user_data User data to pass to callback * * \return Standard Pacemaker return code * \note The caller must use a main loop. This function is not a * stonith_api_operations_t method because it does not need a stonith_t * object and does not go through the fencer, but executes the agent * directly. */ int stonith__metadata_async(const char *agent, int timeout_sec, void (*callback)(int pid, const pcmk__action_result_t *result, void *user_data), void *user_data) { switch (stonith_get_namespace(agent, NULL)) { case st_namespace_rhcs: { stonith_action_t *action = NULL; int rc = pcmk_ok; action = stonith__action_create(agent, "metadata", NULL, 0, timeout_sec, NULL, NULL, NULL); rc = stonith__execute_async(action, user_data, callback, NULL); if (rc != pcmk_ok) { callback(0, stonith__action_result(action), user_data); stonith__destroy_action(action); } return pcmk_legacy2rc(rc); } #if HAVE_STONITH_STONITH_H case st_namespace_lha: // LHA metadata is simply synthesized, so simulate async { pcmk__action_result_t result = { .exit_status = CRM_EX_OK, .execution_status = PCMK_EXEC_DONE, .exit_reason = NULL, .action_stdout = NULL, .action_stderr = NULL, }; stonith__lha_metadata(agent, timeout_sec, &result.action_stdout); callback(0, &result, user_data); pcmk__reset_result(&result); return pcmk_rc_ok; } #endif default: { pcmk__action_result_t result = { .exit_status = CRM_EX_NOSUCH, .execution_status = PCMK_EXEC_ERROR_HARD, .exit_reason = crm_strdup_printf("No such agent '%s'", agent), .action_stdout = NULL, .action_stderr = NULL, }; callback(0, &result, user_data); pcmk__reset_result(&result); return ENOENT; } } } /*! * \internal * \brief Return the exit status from an async action callback * * \param[in] data Callback data * * \return Exit status from callback data */ int stonith__exit_status(stonith_callback_data_t *data) { if ((data == NULL) || (data->opaque == NULL)) { return CRM_EX_ERROR; } return ((pcmk__action_result_t *) data->opaque)->exit_status; } /*! * \internal * \brief Return the execution status from an async action callback * * \param[in] data Callback data * * \return Execution status from callback data */ int stonith__execution_status(stonith_callback_data_t *data) { if ((data == NULL) || (data->opaque == NULL)) { return PCMK_EXEC_UNKNOWN; } return ((pcmk__action_result_t *) data->opaque)->execution_status; } /*! * \internal * \brief Return the exit reason from an async action callback * * \param[in] data Callback data * * \return Exit reason from callback data */ const char * stonith__exit_reason(stonith_callback_data_t *data) { if ((data == NULL) || (data->opaque == NULL)) { return NULL; } return ((pcmk__action_result_t *) data->opaque)->exit_reason; } /*! * \internal * \brief Return the exit status from an event notification * * \param[in] event Event * * \return Exit status from event */ int stonith__event_exit_status(stonith_event_t *event) { if ((event == NULL) || (event->opaque == NULL)) { return CRM_EX_ERROR; } else { struct event_private *event_private = event->opaque; return event_private->result.exit_status; } } /*! * \internal * \brief Return the execution status from an event notification * * \param[in] event Event * * \return Execution status from event */ int stonith__event_execution_status(stonith_event_t *event) { if ((event == NULL) || (event->opaque == NULL)) { return PCMK_EXEC_UNKNOWN; } else { struct event_private *event_private = event->opaque; return event_private->result.execution_status; } } /*! * \internal * \brief Return the exit reason from an event notification * * \param[in] event Event * * \return Exit reason from event */ const char * stonith__event_exit_reason(stonith_event_t *event) { if ((event == NULL) || (event->opaque == NULL)) { return NULL; } else { struct event_private *event_private = event->opaque; return event_private->result.exit_reason; } } /*! * \internal * \brief Return a human-friendly description of a fencing event * * \param[in] event Event to describe * * \return Newly allocated string with description of \p event * \note The caller is responsible for freeing the return value. * This function asserts on memory errors and never returns NULL. */ char * stonith__event_description(stonith_event_t *event) { // Use somewhat readable defaults const char *origin = pcmk__s(event->client_origin, "a client"); const char *origin_node = pcmk__s(event->origin, "a node"); const char *executioner = pcmk__s(event->executioner, "the cluster"); const char *device = pcmk__s(event->device, "unknown"); const char *action = pcmk__s(event->action, event->operation); const char *target = pcmk__s(event->target, "no node"); const char *reason = stonith__event_exit_reason(event); const char *status; if (action == NULL) { action = "(unknown)"; } if (stonith__event_execution_status(event) != PCMK_EXEC_DONE) { status = pcmk_exec_status_str(stonith__event_execution_status(event)); } else if (stonith__event_exit_status(event) != CRM_EX_OK) { status = pcmk_exec_status_str(PCMK_EXEC_ERROR); } else { status = crm_exit_str(CRM_EX_OK); } if (pcmk__str_eq(event->operation, T_STONITH_NOTIFY_HISTORY, pcmk__str_none)) { return crm_strdup_printf("Fencing history may have changed"); } else if (pcmk__str_eq(event->operation, STONITH_OP_DEVICE_ADD, pcmk__str_none)) { return crm_strdup_printf("A fencing device (%s) was added", device); } else if (pcmk__str_eq(event->operation, STONITH_OP_DEVICE_DEL, pcmk__str_none)) { return crm_strdup_printf("A fencing device (%s) was removed", device); } else if (pcmk__str_eq(event->operation, STONITH_OP_LEVEL_ADD, pcmk__str_none)) { return crm_strdup_printf("A fencing topology level (%s) was added", device); } else if (pcmk__str_eq(event->operation, STONITH_OP_LEVEL_DEL, pcmk__str_none)) { return crm_strdup_printf("A fencing topology level (%s) was removed", device); } // event->operation should be T_STONITH_NOTIFY_FENCE at this point return crm_strdup_printf("Operation %s of %s by %s for %s@%s: %s%s%s%s (ref=%s)", action, target, executioner, origin, origin_node, status, ((reason == NULL)? "" : " ("), pcmk__s(reason, ""), ((reason == NULL)? "" : ")"), pcmk__s(event->id, "(none)")); } // Deprecated functions kept only for backward API compatibility // LCOV_EXCL_START const char *get_stonith_provider(const char *agent, const char *provider); const char * get_stonith_provider(const char *agent, const char *provider) { return stonith_namespace2text(stonith_get_namespace(agent, provider)); } // LCOV_EXCL_STOP // End deprecated API