diff --git a/include/crm/common/logging.h b/include/crm/common/logging.h index 9fe47633d7..2878fba8a2 100644 --- a/include/crm/common/logging.h +++ b/include/crm/common/logging.h @@ -1,376 +1,411 @@ /* * Copyright 2004-2023 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU General Public License version 2 * or later (GPLv2+) WITHOUT ANY WARRANTY. */ #ifndef PCMK__CRM_COMMON_LOGGING__H # define PCMK__CRM_COMMON_LOGGING__H # include # 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; void crm_enable_blackbox(int nsig); void crm_disable_blackbox(int nsig); void crm_write_blackbox(int nsig, const struct qb_log_callsite *callsite); void crm_update_callsites(void); void crm_log_deinit(void); /*! * \brief Initializes the logging system and defaults to the least verbose output level * * \param[in] entity If not NULL, will be used as the identity for logging purposes * \param[in] argc The number of command line parameters * \param[in] argv The command line parameter values */ void crm_log_preinit(const char *entity, int argc, char *const *argv); gboolean crm_log_init(const char *entity, uint8_t level, gboolean daemon, gboolean to_stderr, int argc, char **argv, gboolean quiet); void crm_log_args(int argc, char **argv); void crm_log_output_fn(const char *file, const char *function, int line, int level, const char *prefix, const char *output); // Log a block of text line by line #define crm_log_output(level, prefix, output) \ crm_log_output_fn(__FILE__, __func__, __LINE__, level, prefix, output) void crm_bump_log_level(int argc, char **argv); void crm_enable_stderr(int enable); gboolean crm_is_callsite_active(struct qb_log_callsite *cs, uint8_t level, uint32_t tags); /* returns the old value */ unsigned int set_crm_log_level(unsigned int level); unsigned int get_crm_log_level(void); void pcmk_log_xml_impl(uint8_t level, const char *text, const xmlNode *xml); /* * Throughout the macros below, note the leading, pre-comma, space in the * various ' , ##args' occurrences to aid portability across versions of 'gcc'. * https://gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html#Variadic-Macros */ #if defined(__clang__) # define CRM_TRACE_INIT_DATA(name) # else # include // required by QB_LOG_INIT_DATA() macro # define CRM_TRACE_INIT_DATA(name) QB_LOG_INIT_DATA(name) #endif +/*! + * \internal + * \brief Clip log_level to \p uint8_t range + * + * \param[in] level Log level to clip + * + * \return 0 if \p level is less than 0, \p UINT8_MAX if \p level is greater + * than \p UINT8_MAX, or \p level otherwise + */ +/* @COMPAT: Make this function internal at a compatibility break. It's used in + * public macros for now. + */ +static inline uint8_t +pcmk__clip_log_level(int level) +{ + if (level <= 0) { + return 0; + } + if (level >= UINT8_MAX) { + return UINT8_MAX; + } + return level; +} + /* Using "switch" instead of "if" in these macro definitions keeps * static analysis from complaining about constant evaluations */ /*! * \brief Log a message * * \param[in] level Priority at which to log the message * \param[in] fmt printf-style format string literal for message * \param[in] args Any arguments needed by format string - * - * \note This is a macro, and \p level may be evaluated more than once. */ # define do_crm_log(level, fmt, args...) do { \ - switch (level) { \ + uint8_t _level = pcmk__clip_log_level(level); \ + \ + switch (_level) { \ case LOG_STDOUT: \ printf(fmt "\n" , ##args); \ break; \ case LOG_NEVER: \ break; \ default: \ qb_log_from_external_source(__func__, __FILE__, fmt, \ - (level), __LINE__, 0 , ##args); \ + _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. + * \note This does nothing when level is \p LOG_STDOUT. */ # define do_crm_log_unlikely(level, fmt, args...) do { \ - switch (level) { \ + uint8_t _level = pcmk__clip_log_level(level); \ + \ + switch (_level) { \ case LOG_STDOUT: case LOG_NEVER: \ break; \ default: { \ static struct qb_log_callsite *trace_cs = NULL; \ if (trace_cs == NULL) { \ trace_cs = qb_log_callsite_get(__func__, __FILE__, fmt, \ - (level), __LINE__, 0); \ + _level, __LINE__, 0); \ } \ - if (crm_is_callsite_active(trace_cs, (level), 0)) { \ + if (crm_is_callsite_active(trace_cs, _level, 0)) { \ qb_log_from_external_source(__func__, __FILE__, fmt, \ - (level), __LINE__, 0 , ##args); \ + _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 \p level is \p LOG_STDOUT. + * \note This does nothing when \p level is \p LOG_STDOUT. */ # define do_crm_log_xml(level, text, xml) do { \ + uint8_t _level = pcmk__clip_log_level(level); \ static struct qb_log_callsite *xml_cs = NULL; \ \ - switch (level) { \ + switch (_level) { \ case LOG_STDOUT: \ case LOG_NEVER: \ break; \ default: \ if (xml_cs == NULL) { \ xml_cs = qb_log_callsite_get(__func__, __FILE__, \ - "xml-blob", level, \ + "xml-blob", _level, \ __LINE__, 0); \ } \ - if (crm_is_callsite_active(xml_cs, level, 0)) { \ - pcmk_log_xml_impl(level, text, xml); \ + if (crm_is_callsite_active(xml_cs, _level, 0)) { \ + pcmk_log_xml_impl(_level, text, xml); \ } \ break; \ } \ } while(0) /*! * \brief Log a message as if it came from a different code location * * \param[in] level Priority at which to log the message * \param[in] file Source file name to use instead of __FILE__ * \param[in] function Source function name to use instead of __func__ * \param[in] line Source line number to use instead of __line__ * \param[in] fmt printf-style format string literal for message * \param[in] args Any arguments needed by format string - * - * \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) { \ + uint8_t _level = pcmk__clip_log_level(level); \ + \ + switch (_level) { \ case LOG_STDOUT: \ printf(fmt "\n" , ##args); \ break; \ case LOG_NEVER: \ break; \ default: \ - qb_log_from_external_source(function, file, fmt, (level), \ + 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) { \ + uint8_t _level = pcmk__clip_log_level(level); \ + \ + 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);\ + if (_level <= crm_log_level) { \ + fprintf(stderr, fmt ": %s (%d)\n" , ##args, err, \ + errno); \ } \ + /* Pass original level arg since do_crm_log() also declares \ + * _level \ + */ \ do_crm_log((level), fmt ": %s (%d)" , ##args, err, errno); \ } \ break; \ } \ } while (0) /*! * \brief Log a message with a tag (for use with PCMK_trace_tags) * * \param[in] level Priority at which to log the message * \param[in] tag String to tag message with * \param[in] fmt printf-style format string for message * \param[in] args Any arguments needed by format string * - * \note This is a macro, and \p level may be evaluated more than once. - * This does nothing when level is LOG_STDOUT. + * \note This does nothing when level is LOG_STDOUT. */ # define crm_log_tag(level, tag, fmt, args...) do { \ - switch (level) { \ + uint8_t _level = pcmk__clip_log_level(level); \ + \ + switch (_level) { \ case LOG_STDOUT: case LOG_NEVER: \ break; \ default: { \ static struct qb_log_callsite *trace_tag_cs = NULL; \ int converted_tag = g_quark_try_string(tag); \ if (trace_tag_cs == NULL) { \ trace_tag_cs = qb_log_callsite_get(__func__, __FILE__, \ - fmt, (level), __LINE__, converted_tag); \ + fmt, _level, \ + __LINE__, \ + converted_tag); \ } \ - if (crm_is_callsite_active(trace_tag_cs, (level), \ + if (crm_is_callsite_active(trace_tag_cs, _level, \ converted_tag)) { \ qb_log_from_external_source(__func__, __FILE__, fmt, \ - (level), __LINE__, converted_tag , ##args); \ + _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/output_internal.h b/include/crm/common/output_internal.h index 78665408a0..26cd8c42fc 100644 --- a/include/crm/common/output_internal.h +++ b/include/crm/common/output_internal.h @@ -1,948 +1,949 @@ /* * Copyright 2019-2023 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #ifndef PCMK__OUTPUT_INTERNAL__H # define PCMK__OUTPUT_INTERNAL__H # include +# include # include # include # include # include # include #ifdef __cplusplus extern "C" { #endif /** * \file * \brief Formatted output for pacemaker tools */ # define PCMK__API_VERSION "2.29" #if defined(PCMK__WITH_ATTRIBUTE_OUTPUT_ARGS) # define PCMK__OUTPUT_ARGS(ARGS...) __attribute__((output_args(ARGS))) #else # define PCMK__OUTPUT_ARGS(ARGS...) #endif typedef struct pcmk__output_s pcmk__output_t; /*! * \internal * \brief The type of a function that creates a ::pcmk__output_t. * * Instances of this type are passed to pcmk__register_format(), stored in an * internal data structure, and later accessed by pcmk__output_new(). For * examples, see pcmk__mk_xml_output() and pcmk__mk_text_output(). * * \param[in] argv The list of command line arguments. */ typedef pcmk__output_t * (*pcmk__output_factory_t)(char **argv); /*! * \internal * \brief The type of a custom message formatting function. * * These functions are defined by various libraries to support formatting of * types aside from the basic types provided by a ::pcmk__output_t. * * The meaning of the return value will be different for each message. * In general, however, 0 should be returned on success and a positive value * on error. * * \param[in,out] out Output object to use to display message * \param[in,out] args Message-specific arguments needed * * \note These functions must not call va_start or va_end - that is done * automatically before the custom formatting function is called. */ typedef int (*pcmk__message_fn_t)(pcmk__output_t *out, va_list args); /*! * \internal * \brief Internal type for tracking custom messages. * * Each library can register functions that format custom message types. These * are commonly used to handle some library-specific type. Registration is * done by first defining a table of ::pcmk__message_entry_t structures and * then passing that table to pcmk__register_messages(). Separate handlers * can be defined for the same message, but for different formats (xml vs. * text). Unknown formats will be ignored. * * Additionally, a "default" value for fmt_table can be used. In this case, * fn will be registered for all supported formats. It is also possible to * register a default and then override that registration with a format-specific * function if necessary. * * \note The ::pcmk__message_entry_t table is processed in one pass, in order, * from top to bottom. This means later entries with the same message_id will * override previous ones. Thus, any default entry must come before any * format-specific entries for the same message_id. */ typedef struct pcmk__message_entry_s { /*! * \brief The message to be handled. * * This must be the same ID that is passed to the message function of * a ::pcmk__output_t. Unknown message IDs will be ignored. */ const char *message_id; /*! * \brief The format type this handler is for. * * This name must match the fmt_name of the currently active formatter in * order for the registered function to be called. It is valid to have * multiple entries for the same message_id but with different fmt_name * values. */ const char *fmt_name; /*! * \brief The function to be called for message_id given a match on * fmt_name. See comments on ::pcmk__message_fn_t. */ pcmk__message_fn_t fn; } pcmk__message_entry_t; /*! * \internal * \brief This structure contains everything needed to add support for a * single output formatter to a command line program. */ typedef struct pcmk__supported_format_s { /*! * \brief The name of this output formatter, which should match the * fmt_name parameter in some ::pcmk__output_t structure. */ const char *name; /*! * \brief A function that creates a ::pcmk__output_t. */ pcmk__output_factory_t create; /*! * \brief Format-specific command line options. This can be NULL if * no command line options should be supported. */ GOptionEntry *options; } pcmk__supported_format_t; /* The following three blocks need to be updated each time a new base formatter * is added. */ extern GOptionEntry pcmk__html_output_entries[]; extern GOptionEntry pcmk__log_output_entries[]; extern GOptionEntry pcmk__none_output_entries[]; extern GOptionEntry pcmk__text_output_entries[]; extern GOptionEntry pcmk__xml_output_entries[]; pcmk__output_t *pcmk__mk_html_output(char **argv); pcmk__output_t *pcmk__mk_log_output(char **argv); pcmk__output_t *pcmk__mk_none_output(char **argv); pcmk__output_t *pcmk__mk_text_output(char **argv); pcmk__output_t *pcmk__mk_xml_output(char **argv); #define PCMK__SUPPORTED_FORMAT_HTML { "html", pcmk__mk_html_output, pcmk__html_output_entries } #define PCMK__SUPPORTED_FORMAT_LOG { "log", pcmk__mk_log_output, pcmk__log_output_entries } #define PCMK__SUPPORTED_FORMAT_NONE { PCMK__VALUE_NONE, pcmk__mk_none_output, \ pcmk__none_output_entries } #define PCMK__SUPPORTED_FORMAT_TEXT { "text", pcmk__mk_text_output, pcmk__text_output_entries } #define PCMK__SUPPORTED_FORMAT_XML { "xml", pcmk__mk_xml_output, pcmk__xml_output_entries } /*! * \brief This structure contains everything that makes up a single output * formatter. * * Instances of this structure may be created by calling pcmk__output_new() * with the name of the desired formatter. They should later be freed with * pcmk__output_free(). */ struct pcmk__output_s { /*! * \brief The name of this output formatter. */ const char *fmt_name; /*! * \brief Should this formatter supress most output? * * \note This setting is not respected by all formatters. In general, * machine-readable output formats will not support this while * user-oriented formats will. Callers should use is_quiet() * to test whether to print or not. */ bool quiet; /*! * \brief A copy of the request that generated this output. * * In the case of command line usage, this would be the command line * arguments. For other use cases, it could be different. */ gchar *request; /*! * \brief Where output should be written. * * This could be a file handle, or stdout or stderr. This is really only * useful internally. */ FILE *dest; /*! * \brief Custom messages that are currently registered on this formatter. * * Keys are the string message IDs, values are ::pcmk__message_fn_t function * pointers. */ GHashTable *messages; /*! * \brief Implementation-specific private data. * * Each individual formatter may have some private data useful in its * implementation. This points to that data. Callers should not rely on * its contents or structure. */ void *priv; /*! * \internal * \brief Take whatever actions are necessary to prepare out for use. This is * called by pcmk__output_new(). End users should not need to call this. * * \note For formatted output implementers - This function should be written in * such a way that it can be called repeatedly on an already initialized * object without causing problems, or on a previously finished object * without crashing. * * \param[in,out] out The output functions structure. * * \return true on success, false on error. */ bool (*init) (pcmk__output_t *out); /*! * \internal * \brief Free the private formatter-specific data. * * This is called from pcmk__output_free() and does not typically need to be * called directly. * * \param[in,out] out The output functions structure. */ void (*free_priv) (pcmk__output_t *out); /*! * \internal * \brief Take whatever actions are necessary to end formatted output. * * This could include flushing output to a file, but does not include freeing * anything. The finish method can potentially be fairly complicated, adding * additional information to the internal data structures or doing whatever * else. It is therefore suggested that finish only be called once. * * \note The print parameter will only affect those formatters that do all * their output at the end. Console-oriented formatters typically print * a line at a time as they go, so this parameter will not affect them. * Structured formatters will honor it, however. * * \note The copy_dest parameter does not apply to all formatters. Console- * oriented formatters do not build up a structure as they go, and thus * do not have anything to return. Structured formatters will honor it, * however. Note that each type of formatter will return a different * type of value in this parameter. To use this parameter, call this * function like so: * * \code * xmlNode *dest = NULL; * out->finish(out, exit_code, false, (void **) &dest); * \endcode * * \param[in,out] out The output functions structure. * \param[in] exit_status The exit value of the whole program. * \param[in] print Whether this function should write any output. * \param[out] copy_dest A destination to store a copy of the internal * data structure for this output, or NULL if no * copy is required. The caller should free this * memory when done with it. */ void (*finish) (pcmk__output_t *out, crm_exit_t exit_status, bool print, void **copy_dest); /*! * \internal * \brief Finalize output and then immediately set back up to start a new set * of output. * * This is conceptually the same as calling finish and then init, though in * practice more be happening behind the scenes. * * \note This function differs from finish in that no exit_status is added. * The idea is that the program is not shutting down, so there is not * yet a final exit code. Call finish on the last time through if this * is needed. * * \param[in,out] out The output functions structure. */ void (*reset) (pcmk__output_t *out); /*! * \internal * \brief Register a custom message. * * \param[in,out] out The output functions structure. * \param[in] message_id The name of the message to register. This name * will be used as the message_id parameter to the * message function in order to call the custom * format function. * \param[in] fn The custom format function to call for message_id. */ void (*register_message) (pcmk__output_t *out, const char *message_id, pcmk__message_fn_t fn); /*! * \internal * \brief Call a previously registered custom message. * * \param[in,out] out The output functions structure. * \param[in] message_id The name of the message to call. This name must * be the same as the message_id parameter of some * previous call to register_message. * \param[in] ... Arguments to be passed to the registered function. * * \return A standard Pacemaker return code. Generally: 0 if a function was * registered for the message, that function was called, and returned * successfully; EINVAL if no function was registered; or pcmk_rc_no_output * if a function was called but produced no output. */ int (*message) (pcmk__output_t *out, const char *message_id, ...); /*! * \internal * \brief Format the output of a completed subprocess. * * \param[in,out] out The output functions structure. * \param[in] exit_status The exit value of the subprocess. * \param[in] proc_stdout stdout from the completed subprocess. * \param[in] proc_stderr stderr from the completed subprocess. */ void (*subprocess_output) (pcmk__output_t *out, int exit_status, const char *proc_stdout, const char *proc_stderr); /*! * \internal * \brief Format version information. This is useful for the --version * argument of command line tools. * * \param[in,out] out The output functions structure. * \param[in] extended Add additional version information. */ void (*version) (pcmk__output_t *out, bool extended); /*! * \internal * \brief Format an informational message that should be shown to * to an interactive user. Not all formatters will do this. * * \note A newline will automatically be added to the end of the format * string, so callers should not include a newline. * * \note It is possible for a formatter that supports this method to * still not print anything out if is_quiet returns true. * * \param[in,out] out The output functions structure. * \param[in] buf The message to be printed. * \param[in] ... Arguments to be formatted. * * \return A standard Pacemaker return code. Generally: pcmk_rc_ok * if output was produced and pcmk_rc_no_output if it was not. * As not all formatters implement this function, those that * do not will always just return pcmk_rc_no_output. */ int (*info) (pcmk__output_t *out, const char *format, ...) G_GNUC_PRINTF(2, 3); /*! * \internal * \brief Like \p info() but for messages that should appear only * transiently. Not all formatters will do this. * * The originally envisioned use case is for console output, where a * transient status-related message may be quickly overwritten by a refresh. * * \param[in,out] out The output functions structure. * \param[in] format The format string of the message to be printed. * \param[in] ... Arguments to be formatted. * * \return A standard Pacemaker return code. Generally: \p pcmk_rc_ok if * output was produced and \p pcmk_rc_no_output if it was not. As * not all formatters implement this function, those that do not * will always just return \p pcmk_rc_no_output. */ int (*transient) (pcmk__output_t *out, const char *format, ...) G_GNUC_PRINTF(2, 3); /*! * \internal * \brief Format an error message that should be shown to an interactive * user. Not all formatters will do this. * * \note A newline will automatically be added to the end of the format * string, so callers should not include a newline. * * \note Formatters that support this method should always generate output, * even if is_quiet returns true. * * \param[in,out] out The output functions structure. * \param[in] buf The message to be printed. * \param[in] ... Arguments to be formatted. */ void (*err) (pcmk__output_t *out, const char *format, ...) G_GNUC_PRINTF(2, 3); /*! * \internal * \brief Format already formatted XML. * * \param[in,out] out The output functions structure. * \param[in] name A name to associate with the XML. * \param[in] buf The XML in a string. */ void (*output_xml) (pcmk__output_t *out, const char *name, const char *buf); /*! * \internal * \brief Start a new list of items. * * \note For text output, this corresponds to another level of indentation. For * XML output, this corresponds to wrapping any following output in another * layer of tags. * * \note If singular_noun and plural_noun are non-NULL, calling end_list will * result in a summary being added. * * \param[in,out] out The output functions structure. * \param[in] singular_noun When outputting the summary for a list with * one item, the noun to use. * \param[in] plural_noun When outputting the summary for a list with * more than one item, the noun to use. * \param[in] format The format string. * \param[in] ... Arguments to be formatted. */ void (*begin_list) (pcmk__output_t *out, const char *singular_noun, const char *plural_noun, const char *format, ...) G_GNUC_PRINTF(4, 5); /*! * \internal * \brief Format a single item in a list. * * \param[in,out] out The output functions structure. * \param[in] name A name to associate with this item. * \param[in] format The format string. * \param[in] ... Arguments to be formatted. */ void (*list_item) (pcmk__output_t *out, const char *name, const char *format, ...) G_GNUC_PRINTF(3, 4); /*! * \internal * \brief Increment the internal counter of the current list's length. * * Typically, this counter is maintained behind the scenes as a side effect * of calling list_item(). However, custom functions that maintain lists * some other way will need to manage this counter manually. This is * useful for implementing custom message functions and should not be * needed otherwise. * * \param[in,out] out The output functions structure. */ void (*increment_list) (pcmk__output_t *out); /*! * \internal * \brief Conclude a list. * * \note If begin_list was called with non-NULL for both the singular_noun * and plural_noun arguments, this function will output a summary. * Otherwise, no summary will be added. * * \param[in,out] out The output functions structure. */ void (*end_list) (pcmk__output_t *out); /*! * \internal * \brief Should anything be printed to the user? * * \note This takes into account both the \p quiet value as well as the * current formatter. * * \param[in,out] out The output functions structure. * * \return true if output should be supressed, false otherwise. */ bool (*is_quiet) (pcmk__output_t *out); /*! * \internal * \brief Output a spacer. Not all formatters will do this. * * \param[in,out] out The output functions structure. */ void (*spacer) (pcmk__output_t *out); /*! * \internal * \brief Output a progress indicator. This is likely only useful for * plain text, console based formatters. * * \param[in,out] out The output functions structure * \param[in] end If true, output a newline afterwards (this should * only be used the last time this function is called) * */ void (*progress) (pcmk__output_t *out, bool end); /*! * \internal * \brief Prompt the user for input. Not all formatters will do this. * * \note This function is part of pcmk__output_t, but unlike all other * function it does not take that as an argument. In general, a * prompt will go directly to the screen and therefore bypass any * need to use the formatted output code to decide where and how * to display. * * \param[in] prompt The prompt to display. This is required. * \param[in] echo If true, echo the user's input to the screen. Set * to false for password entry. * \param[out] dest Where to store the user's response. This is * required. */ void (*prompt) (const char *prompt, bool echo, char **dest); }; /*! * \internal * \brief Call a formatting function for a previously registered message. * * \note This function is for implementing custom formatters. It should not * be called directly. Instead, call out->message. * * \param[in,out] out The output functions structure. * \param[in] message_id The message to be handled. Unknown messages * will be ignored. * \param[in] ... Arguments to be passed to the registered function. */ int pcmk__call_message(pcmk__output_t *out, const char *message_id, ...); /*! * \internal * \brief Free a ::pcmk__output_t structure that was previously created by * pcmk__output_new(). * * \note While the create and finish functions are designed in such a way that * they can be called repeatedly, this function will completely free the * memory of the object. Once this function has been called, producing * more output requires starting over from pcmk__output_new(). * * \param[in,out] out The output structure. */ void pcmk__output_free(pcmk__output_t *out); /*! * \internal * \brief Create a new ::pcmk__output_t structure. * * \param[in,out] out The destination of the new ::pcmk__output_t. * \param[in] fmt_name How should output be formatted? * \param[in] filename Where should formatted output be written to? This * can be a filename (which will be overwritten if it * already exists), or NULL or "-" for stdout. For no * output, pass a filename of "/dev/null". * \param[in] argv The list of command line arguments. * * \return Standard Pacemaker return code */ int pcmk__output_new(pcmk__output_t **out, const char *fmt_name, const char *filename, char **argv); /*! * \internal * \brief Register a new output formatter, making it available for use * the same as a base formatter. * * \param[in,out] group A ::GOptionGroup that formatted output related command * line arguments should be added to. This can be NULL * for use outside of command line programs. * \param[in] name The name of the format. This will be used to select a * format from command line options and for displaying help. * \param[in] create A function that creates a ::pcmk__output_t. * \param[in] options Format-specific command line options. These will be * added to the context. This argument can also be NULL. * * \return Standard Pacemaker return code */ int pcmk__register_format(GOptionGroup *group, const char *name, pcmk__output_factory_t create, const GOptionEntry *options); /*! * \internal * \brief Register an entire table of output formatters at once. * * \param[in,out] group A ::GOptionGroup that formatted output related command * line arguments should be added to. This can be NULL * for use outside of command line programs. * \param[in] table An array of ::pcmk__supported_format_t which should * all be registered. This array must be NULL-terminated. * */ void pcmk__register_formats(GOptionGroup *group, const pcmk__supported_format_t *table); /*! * \internal * \brief Unregister a previously registered table of custom formatting * functions and destroy the internal data structures associated with them. */ void pcmk__unregister_formats(void); /*! * \internal * \brief Register a function to handle a custom message. * * \note This function is for implementing custom formatters. It should not * be called directly. Instead, call out->register_message. * * \param[in,out] out The output functions structure. * \param[in] message_id The message to be handled. * \param[in] fn The custom format function to call for message_id. */ void pcmk__register_message(pcmk__output_t *out, const char *message_id, pcmk__message_fn_t fn); /*! * \internal * \brief Register an entire table of custom formatting functions at once. * * This table can contain multiple formatting functions for the same message ID * if they are for different format types. * * \param[in,out] out The output functions structure. * \param[in] table An array of ::pcmk__message_entry_t values which should * all be registered. This array must be NULL-terminated. */ void pcmk__register_messages(pcmk__output_t *out, const pcmk__message_entry_t *table); /* Functions that are useful for implementing custom message formatters */ /*! * \internal * \brief A printf-like function. * * This function writes to out->dest and indents the text to the current level * of the text formatter's nesting. This function should be used when implementing * custom message functions for the text output format. It should not be used * for any other purpose. * * Typically, this function should be used instead of printf. * * \param[in,out] out The output functions structure. * \param[in] format The format string. * \param[in] ... Arguments to be passed to the format string. */ void pcmk__indented_printf(pcmk__output_t *out, const char *format, ...) G_GNUC_PRINTF(2, 3); /*! * \internal * \brief A vprintf-like function. * * This function is like pcmk__indented_printf(), except it takes a va_list instead * of a list of arguments. This function should be used when implementing custom * functions for the text output format. It should not be used for any other purpose. * * Typically, this function should be used instead of vprintf. * * \param[in,out] out The output functions structure. * \param[in] format The format string. * \param[in] args A list of arguments to apply to the format string. */ void pcmk__indented_vprintf(pcmk__output_t *out, const char *format, va_list args) G_GNUC_PRINTF(2, 0); /*! * \internal * \brief A printf-like function. * * This function writes to out->dest without indenting the text. This function * should be used when implementing custom message functions for the text output * format. It should not be used for any other purpose. * * \param[in,out] out The output functions structure. * \param[in] format The format string. * \param[in] ... Arguments to be passed to the format string. */ void pcmk__formatted_printf(pcmk__output_t *out, const char *format, ...) G_GNUC_PRINTF(2, 3); /*! * \internal * \brief A vprintf-like function. * * This function is like pcmk__formatted_printf(), except it takes a va_list instead * of a list of arguments. This function should be used when implementing custom * message functions for the text output format. It should not be used for any * other purpose. * * \param[in,out] out The output functions structure. * \param[in] format The format string. * \param[in] args A list of arguments to apply to the format string. */ void pcmk__formatted_vprintf(pcmk__output_t *out, const char *format, va_list args) G_GNUC_PRINTF(2, 0); /*! * \internal * \brief Prompt the user for input. * * \param[in] prompt The prompt to display * \param[in] echo If true, echo the user's input to the screen. Set * to false for password entry. * \param[out] dest Where to store the user's response. */ void pcmk__text_prompt(const char *prompt, bool echo, char **dest); /*! * \internal * \brief Get the log level used by the formatted output logger * * \param[in] out Output object * * \return Log level used by \p out */ -int +uint8_t pcmk__output_get_log_level(const pcmk__output_t *out); /*! * \internal * \brief Set the log level used by the formatted output logger. * * \param[in,out] out The output functions structure. * \param[in] log_level The log level constant (LOG_INFO, LOG_ERR, etc.) * to use. * * \note By default, LOG_INFO is used. * \note Almost all formatted output messages will respect this setting. * However, out->err will always log at LOG_ERR. */ void -pcmk__output_set_log_level(pcmk__output_t *out, int log_level); +pcmk__output_set_log_level(pcmk__output_t *out, uint8_t log_level); /*! * \internal * \brief Create and return a new XML node with the given name, as a child of the * current list parent. The new node is then added as the new list parent, * meaning all subsequent nodes will be its children. This is used when * implementing custom functions. * * \param[in,out] out The output functions structure. * \param[in] name The name of the node to be created. * \param[in] ... Name/value pairs to set as XML properties. */ xmlNodePtr pcmk__output_xml_create_parent(pcmk__output_t *out, const char *name, ...) G_GNUC_NULL_TERMINATED; /*! * \internal * \brief Add a copy of the given node as a child of the current list parent. * This is used when implementing custom message functions. * * \param[in,out] out The output functions structure. * \param[in] node An XML node to copy as a child. */ void pcmk__output_xml_add_node_copy(pcmk__output_t *out, xmlNodePtr node); /*! * \internal * \brief Create and return a new XML node with the given name, as a child of the * current list parent. This is used when implementing custom functions. * * \param[in,out] out The output functions structure. * \param[in] name The name of the node to be created. * \param[in] ... Name/value pairs to set as XML properties. */ xmlNodePtr pcmk__output_create_xml_node(pcmk__output_t *out, const char *name, ...) G_GNUC_NULL_TERMINATED; /*! * \internal * \brief Like pcmk__output_create_xml_node(), but add the given text content to the * new node. * * \param[in,out] out The output functions structure. * \param[in] name The name of the node to be created. * \param[in] content The text content of the node. */ xmlNodePtr pcmk__output_create_xml_text_node(pcmk__output_t *out, const char *name, const char *content); /*! * \internal * \brief Push a parent XML node onto the stack. This is used when implementing * custom message functions. * * The XML output formatter maintains an internal stack to keep track of which nodes * are parents in order to build up the tree structure. This function can be used * to temporarily push a new node onto the stack. After calling this function, any * other formatting functions will have their nodes added as children of this new * parent. * * \param[in,out] out The output functions structure * \param[in] parent XML node to add */ void pcmk__output_xml_push_parent(pcmk__output_t *out, xmlNodePtr parent); /*! * \internal * \brief Pop a parent XML node onto the stack. This is used when implementing * custom message functions. * * This function removes a parent node from the stack. See pcmk__xml_push_parent() * for more details. * * \note Little checking is done with this function. Be sure you only pop parents * that were previously pushed. In general, it is best to keep the code between * push and pop simple. * * \param[in,out] out The output functions structure. */ void pcmk__output_xml_pop_parent(pcmk__output_t *out); /*! * \internal * \brief Peek a parent XML node onto the stack. This is used when implementing * custom message functions. * * This function peeks a parent node on stack. See pcmk__xml_push_parent() * for more details. It has no side-effect and can be called for an empty stack. * * \note Little checking is done with this function. * * \param[in,out] out The output functions structure. * * \return NULL if stack is empty, otherwise the parent of the stack. */ xmlNodePtr pcmk__output_xml_peek_parent(pcmk__output_t *out); /*! * \internal * \brief Create a new XML node consisting of the provided text inside an HTML * element node of the given name. * * \param[in,out] out The output functions structure. * \param[in] element_name The name of the new HTML element. * \param[in] id The CSS ID selector to apply to this element. * If NULL, no ID is added. * \param[in] class_name The CSS class selector to apply to this element. * If NULL, no class is added. * \param[in] text The text content of the node. */ xmlNodePtr pcmk__output_create_html_node(pcmk__output_t *out, const char *element_name, const char *id, const char *class_name, const char *text); /*! * \internal * \brief Add an HTML tag to the 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] out The output functions structure. If NULL, any errors * will simply be printed to stderr. */ void pcmk__output_and_clear_error(GError *error, pcmk__output_t *out); int pcmk__xml_output_new(pcmk__output_t **out, xmlNodePtr *xml); void pcmk__xml_output_finish(pcmk__output_t *out, xmlNodePtr *xml); int pcmk__log_output_new(pcmk__output_t **out); int pcmk__text_output_new(pcmk__output_t **out, const char *filename); #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/lib/common/output_log.c b/lib/common/output_log.c index 8e808da6dd..aca168d0b4 100644 --- a/lib/common/output_log.c +++ b/lib/common/output_log.c @@ -1,353 +1,353 @@ /* * Copyright 2019-2023 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #include #include #include #include #include #include GOptionEntry pcmk__log_output_entries[] = { { NULL } }; typedef struct private_data_s { /* gathered in log_begin_list */ GQueue/**/ *prefixes; - int log_level; + uint8_t log_level; } private_data_t; static void log_subprocess_output(pcmk__output_t *out, int exit_status, const char *proc_stdout, const char *proc_stderr) { /* This function intentionally left blank */ } static void log_free_priv(pcmk__output_t *out) { private_data_t *priv = NULL; if (out == NULL || out->priv == NULL) { return; } priv = out->priv; g_queue_free(priv->prefixes); free(priv); out->priv = NULL; } static bool log_init(pcmk__output_t *out) { private_data_t *priv = NULL; CRM_ASSERT(out != NULL); /* If log_init was previously called on this output struct, just return. */ if (out->priv != NULL) { return true; } out->priv = calloc(1, sizeof(private_data_t)); if (out->priv == NULL) { return false; } priv = out->priv; priv->prefixes = g_queue_new(); priv->log_level = LOG_INFO; return true; } static void log_finish(pcmk__output_t *out, crm_exit_t exit_status, bool print, void **copy_dest) { /* This function intentionally left blank */ } static void log_reset(pcmk__output_t *out) { CRM_ASSERT(out != NULL); out->dest = freopen(NULL, "w", out->dest); CRM_ASSERT(out->dest != NULL); log_free_priv(out); log_init(out); } static void log_version(pcmk__output_t *out, bool extended) { private_data_t *priv = NULL; CRM_ASSERT(out != NULL && out->priv != NULL); priv = out->priv; if (extended) { do_crm_log(priv->log_level, "Pacemaker %s (Build: %s): %s", PACEMAKER_VERSION, BUILD_VERSION, CRM_FEATURES); } else { do_crm_log(priv->log_level, "Pacemaker %s", PACEMAKER_VERSION); do_crm_log(priv->log_level, "Written by Andrew Beekhof and" "the Pacemaker project contributors"); } } G_GNUC_PRINTF(2, 3) static void log_err(pcmk__output_t *out, const char *format, ...) { va_list ap; char* buffer = NULL; int len = 0; CRM_ASSERT(out != NULL); va_start(ap, format); /* Informational output does not get indented, to separate it from other * potentially indented list output. */ len = vasprintf(&buffer, format, ap); CRM_ASSERT(len >= 0); va_end(ap); crm_err("%s", buffer); free(buffer); } static void log_output_xml(pcmk__output_t *out, const char *name, const char *buf) { xmlNodePtr node = NULL; private_data_t *priv = NULL; CRM_ASSERT(out != NULL && out->priv != NULL); priv = out->priv; node = create_xml_node(NULL, name); xmlNodeSetContent(node, (pcmkXmlStr) buf); do_crm_log_xml(priv->log_level, name, node); free(node); } G_GNUC_PRINTF(4, 5) static void log_begin_list(pcmk__output_t *out, const char *singular_noun, const char *plural_noun, const char *format, ...) { int len = 0; va_list ap; char* buffer = NULL; private_data_t *priv = NULL; CRM_ASSERT(out != NULL && out->priv != NULL); priv = out->priv; va_start(ap, format); len = vasprintf(&buffer, format, ap); CRM_ASSERT(len >= 0); va_end(ap); /* Don't skip empty prefixes, * otherwise there will be mismatch * in the log_end_list */ if(strcmp(buffer, "") == 0) { /* nothing */ } g_queue_push_tail(priv->prefixes, buffer); } G_GNUC_PRINTF(3, 4) static void log_list_item(pcmk__output_t *out, const char *name, const char *format, ...) { int len = 0; va_list ap; private_data_t *priv = NULL; char prefix[LINE_MAX] = { 0 }; int offset = 0; char* buffer = NULL; CRM_ASSERT(out != NULL && out->priv != NULL); priv = out->priv; for (GList* gIter = priv->prefixes->head; gIter; gIter = gIter->next) { if (strcmp(prefix, "") != 0) { offset += snprintf(prefix + offset, LINE_MAX - offset, ": %s", (char *)gIter->data); } else { offset = snprintf(prefix, LINE_MAX, "%s", (char *)gIter->data); } } va_start(ap, format); len = vasprintf(&buffer, format, ap); CRM_ASSERT(len >= 0); va_end(ap); if (strcmp(buffer, "") != 0) { /* We don't want empty messages */ if ((name != NULL) && (strcmp(name, "") != 0)) { if (strcmp(prefix, "") != 0) { do_crm_log(priv->log_level, "%s: %s: %s", prefix, name, buffer); } else { do_crm_log(priv->log_level, "%s: %s", name, buffer); } } else { if (strcmp(prefix, "") != 0) { do_crm_log(priv->log_level, "%s: %s", prefix, buffer); } else { do_crm_log(priv->log_level, "%s", buffer); } } } free(buffer); } static void log_end_list(pcmk__output_t *out) { private_data_t *priv = NULL; CRM_ASSERT(out != NULL && out->priv != NULL); priv = out->priv; if (priv->prefixes == NULL) { return; } CRM_ASSERT(priv->prefixes->tail != NULL); free((char *)priv->prefixes->tail->data); g_queue_pop_tail(priv->prefixes); } G_GNUC_PRINTF(2, 3) static int log_info(pcmk__output_t *out, const char *format, ...) { private_data_t *priv = NULL; int len = 0; va_list ap; char* buffer = NULL; CRM_ASSERT(out != NULL && out->priv != NULL); priv = out->priv; va_start(ap, format); len = vasprintf(&buffer, format, ap); CRM_ASSERT(len >= 0); va_end(ap); do_crm_log(priv->log_level, "%s", buffer); free(buffer); return pcmk_rc_ok; } G_GNUC_PRINTF(2, 3) static int log_transient(pcmk__output_t *out, const char *format, ...) { private_data_t *priv = NULL; int len = 0; va_list ap; char *buffer = NULL; CRM_ASSERT(out != NULL && out->priv != NULL); priv = out->priv; va_start(ap, format); len = vasprintf(&buffer, format, ap); CRM_ASSERT(len >= 0); va_end(ap); do_crm_log(QB_MAX(priv->log_level, LOG_DEBUG), "%s", buffer); free(buffer); return pcmk_rc_ok; } static bool log_is_quiet(pcmk__output_t *out) { return false; } static void log_spacer(pcmk__output_t *out) { /* This function intentionally left blank */ } static void log_progress(pcmk__output_t *out, bool end) { /* This function intentionally left blank */ } static void log_prompt(const char *prompt, bool echo, char **dest) { /* This function intentionally left blank */ } pcmk__output_t * pcmk__mk_log_output(char **argv) { pcmk__output_t *retval = calloc(1, sizeof(pcmk__output_t)); if (retval == NULL) { return NULL; } retval->fmt_name = "log"; retval->request = pcmk__quote_cmdline(argv); retval->init = log_init; retval->free_priv = log_free_priv; retval->finish = log_finish; retval->reset = log_reset; retval->register_message = pcmk__register_message; retval->message = pcmk__call_message; retval->subprocess_output = log_subprocess_output; retval->version = log_version; retval->info = log_info; retval->transient = log_transient; retval->err = log_err; retval->output_xml = log_output_xml; retval->begin_list = log_begin_list; retval->list_item = log_list_item; retval->end_list = log_end_list; retval->is_quiet = log_is_quiet; retval->spacer = log_spacer; retval->progress = log_progress; retval->prompt = log_prompt; return retval; } -int +uint8_t pcmk__output_get_log_level(const pcmk__output_t *out) { private_data_t *priv = NULL; CRM_ASSERT((out != NULL) && (out->priv != NULL)); CRM_CHECK(pcmk__str_eq(out->fmt_name, "log", pcmk__str_none), return 0); priv = out->priv; return priv->log_level; } void -pcmk__output_set_log_level(pcmk__output_t *out, int log_level) { +pcmk__output_set_log_level(pcmk__output_t *out, uint8_t log_level) { private_data_t *priv = NULL; CRM_ASSERT(out != NULL && out->priv != NULL); CRM_CHECK(pcmk__str_eq(out->fmt_name, "log", pcmk__str_none), return); priv = out->priv; priv->log_level = log_level; } diff --git a/lib/common/xml_display.c b/lib/common/xml_display.c index fb8dd31698..7c237bc396 100644 --- a/lib/common/xml_display.c +++ b/lib/common/xml_display.c @@ -1,511 +1,514 @@ /* * Copyright 2023 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #include #include #include #include #include #include // PCMK__XML_LOG_BASE, etc. #include "crmcommon_private.h" static void show_xml_node(pcmk__output_t *out, GString *buffer, const char *prefix, const xmlNode *data, int depth, uint32_t options); // Log an XML library error void pcmk__log_xmllib_err(void *ctx, const char *fmt, ...) { va_list ap; va_start(ap, fmt); pcmk__if_tracing( { PCMK__XML_LOG_BASE(LOG_ERR, TRUE, crm_abort(__FILE__, __PRETTY_FUNCTION__, __LINE__, "xml library error", TRUE, TRUE), "XML Error: ", fmt, ap); }, { PCMK__XML_LOG_BASE(LOG_ERR, TRUE, 0, "XML Error: ", fmt, ap); } ); va_end(ap); } /*! * \internal * \brief Output an XML comment with depth-based indentation * * \param[in,out] out Output object * \param[in] data XML node to output * \param[in] depth Current indentation level * \param[in] options Group of \p pcmk__xml_fmt_options flags * * \note This currently produces output only for text-like output objects. */ static void show_xml_comment(pcmk__output_t *out, const xmlNode *data, int depth, uint32_t options) { if (pcmk_is_set(options, pcmk__xml_fmt_open)) { out->info(out, "%*s", pcmk_is_set(options, pcmk__xml_fmt_pretty)? (2 * depth) : 0, "", (const char *) data->content); } } /*! * \internal * \brief Output an XML element in a formatted way * * \param[in,out] out Output object * \param[in,out] buffer Where to build output strings * \param[in] prefix String to prepend to every line of output * \param[in] data XML node to output * \param[in] depth Current indentation level * \param[in] options Group of \p pcmk__xml_fmt_options flags * * \note This is a recursive helper function for \p show_xml_node(). * \note This currently produces output only for text-like output objects. * \note \p buffer may be overwritten many times. The caller is responsible for * freeing it using \p g_string_free() but should not rely on its * contents. */ static void show_xml_element(pcmk__output_t *out, GString *buffer, const char *prefix, const xmlNode *data, int depth, uint32_t options) { const char *name = crm_element_name(data); int spaces = pcmk_is_set(options, pcmk__xml_fmt_pretty)? (2 * depth) : 0; if (pcmk_is_set(options, pcmk__xml_fmt_open)) { const char *hidden = crm_element_value(data, "hidden"); g_string_truncate(buffer, 0); for (int lpc = 0; lpc < spaces; lpc++) { g_string_append_c(buffer, ' '); } pcmk__g_strcat(buffer, "<", name, NULL); for (const xmlAttr *attr = pcmk__xe_first_attr(data); attr != NULL; attr = attr->next) { xml_node_private_t *nodepriv = attr->_private; const char *p_name = (const char *) attr->name; const char *p_value = pcmk__xml_attr_value(attr); char *p_copy = NULL; if (pcmk_is_set(nodepriv->flags, pcmk__xf_deleted)) { continue; } // @COMPAT Remove when v1 patchsets are removed if (pcmk_any_flags_set(options, pcmk__xml_fmt_diff_plus |pcmk__xml_fmt_diff_minus) && (strcmp(XML_DIFF_MARKER, p_name) == 0)) { continue; } if ((hidden != NULL) && (p_name[0] != '\0') && (strstr(hidden, p_name) != NULL)) { pcmk__str_update(&p_copy, "*****"); } else { p_copy = crm_xml_escape(p_value); } pcmk__g_strcat(buffer, " ", p_name, "=\"", pcmk__s(p_copy, ""), "\"", NULL); free(p_copy); } if (xml_has_children(data) && pcmk_is_set(options, pcmk__xml_fmt_children)) { g_string_append_c(buffer, '>'); } else { g_string_append(buffer, "/>"); } out->info(out, "%s%s%s", pcmk__s(prefix, ""), pcmk__str_empty(prefix)? "" : " ", buffer->str); } if (!xml_has_children(data)) { return; } if (pcmk_is_set(options, pcmk__xml_fmt_children)) { for (const xmlNode *child = pcmk__xml_first_child(data); child != NULL; child = pcmk__xml_next(child)) { show_xml_node(out, buffer, prefix, child, depth + 1, options|pcmk__xml_fmt_open|pcmk__xml_fmt_close); } } if (pcmk_is_set(options, pcmk__xml_fmt_close)) { out->info(out, "%s%s%*s", pcmk__s(prefix, ""), pcmk__str_empty(prefix)? "" : " ", spaces, "", name); } } /*! * \internal * \brief Output an XML element or comment in a formatted way * * \param[in,out] out Output object * \param[in,out] buffer Where to build output strings * \param[in] prefix String to prepend to every line of output * \param[in] data XML node to log * \param[in] depth Current indentation level * \param[in] options Group of \p pcmk__xml_fmt_options flags * * \note This is a recursive helper function for \p pcmk__xml_show(). * \note This currently produces output only for text-like output objects. * \note \p buffer may be overwritten many times. The caller is responsible for * freeing it using \p g_string_free() but should not rely on its * contents. */ static void show_xml_node(pcmk__output_t *out, GString *buffer, const char *prefix, const xmlNode *data, int depth, uint32_t options) { if (data == NULL) { return; } switch (data->type) { case XML_COMMENT_NODE: show_xml_comment(out, data, depth, options); break; case XML_ELEMENT_NODE: show_xml_element(out, buffer, prefix, data, depth, options); break; default: break; } } /*! * \internal * \brief Output an XML element or comment in a formatted way * * \param[in,out] out Output object * \param[in] prefix String to prepend to every line of output * \param[in] data XML node to output * \param[in] depth Current nesting level * \param[in] options Group of \p pcmk__xml_fmt_options flags * * \note This currently produces output only for text-like output objects. */ void pcmk__xml_show(pcmk__output_t *out, const char *prefix, const xmlNode *data, int depth, uint32_t options) { GString *buffer = NULL; CRM_ASSERT(out != NULL); CRM_CHECK(depth >= 0, depth = 0); /* Allocate a buffer once, for show_xml_node() to truncate and reuse in * recursive calls */ buffer = g_string_sized_new(1024); show_xml_node(out, buffer, prefix, data, depth, options); g_string_free(buffer, TRUE); } /*! * \internal * \brief Output XML portions that have been marked as changed * * \param[in,out] out Output object * \param[in] data XML node to output * \param[in] depth Current indentation level * \param[in] options Group of \p pcmk__xml_fmt_options flags * * \note This is a recursive helper for \p pcmk__xml_show_changes(), showing * changes to \p data and its children. * \note This currently produces output only for text-like output objects. */ static void show_xml_changes_recursive(pcmk__output_t *out, const xmlNode *data, int depth, uint32_t options) { /* @COMPAT: When log_data_element() is removed, we can remove the options * argument here and instead hard-code pcmk__xml_log_pretty. */ xml_node_private_t *nodepriv = (xml_node_private_t *) data->_private; if (pcmk_all_flags_set(nodepriv->flags, pcmk__xf_dirty|pcmk__xf_created)) { // Newly created pcmk__xml_show(out, PCMK__XML_PREFIX_CREATED, data, depth, options |pcmk__xml_fmt_open |pcmk__xml_fmt_children |pcmk__xml_fmt_close); return; } if (pcmk_is_set(nodepriv->flags, pcmk__xf_dirty)) { // Modified or moved bool pretty = pcmk_is_set(options, pcmk__xml_fmt_pretty); int spaces = pretty? (2 * depth) : 0; const char *prefix = PCMK__XML_PREFIX_MODIFIED; if (pcmk_is_set(nodepriv->flags, pcmk__xf_moved)) { prefix = PCMK__XML_PREFIX_MOVED; } // Log opening tag pcmk__xml_show(out, prefix, data, depth, options|pcmk__xml_fmt_open); // Log changes to attributes for (const xmlAttr *attr = pcmk__xe_first_attr(data); attr != NULL; attr = attr->next) { const char *name = (const char *) attr->name; nodepriv = attr->_private; if (pcmk_is_set(nodepriv->flags, pcmk__xf_deleted)) { const char *value = crm_element_value(data, name); out->info(out, "%s %*s @%s=%s", PCMK__XML_PREFIX_DELETED, spaces, "", name, value); } else if (pcmk_is_set(nodepriv->flags, pcmk__xf_dirty)) { const char *value = crm_element_value(data, name); if (pcmk_is_set(nodepriv->flags, pcmk__xf_created)) { prefix = PCMK__XML_PREFIX_CREATED; } else if (pcmk_is_set(nodepriv->flags, pcmk__xf_modified)) { prefix = PCMK__XML_PREFIX_MODIFIED; } else if (pcmk_is_set(nodepriv->flags, pcmk__xf_moved)) { prefix = PCMK__XML_PREFIX_MOVED; } else { prefix = PCMK__XML_PREFIX_MODIFIED; } out->info(out, "%s %*s @%s=%s", prefix, spaces, "", name, value); } } // Log changes to children for (const xmlNode *child = pcmk__xml_first_child(data); child != NULL; child = pcmk__xml_next(child)) { show_xml_changes_recursive(out, child, depth + 1, options); } // Log closing tag pcmk__xml_show(out, PCMK__XML_PREFIX_MODIFIED, data, depth, options|pcmk__xml_fmt_close); } else { // This node hasn't changed, but check its children for (const xmlNode *child = pcmk__xml_first_child(data); child != NULL; child = pcmk__xml_next(child)) { show_xml_changes_recursive(out, child, depth + 1, options); } } } /*! * \internal * \brief Output changes to an XML node and any children * * \param[in,out] out Output object * \param[in] xml XML node to output * * \note This currently produces output only for text-like output objects. */ void pcmk__xml_show_changes(pcmk__output_t *out, const xmlNode *xml) { xml_doc_private_t *docpriv = NULL; CRM_ASSERT(out != NULL); CRM_ASSERT(xml != NULL); CRM_ASSERT(xml->doc != NULL); docpriv = xml->doc->_private; if (!pcmk_is_set(docpriv->flags, pcmk__xf_dirty)) { return; } for (const GList *iter = docpriv->deleted_objs; iter != NULL; iter = iter->next) { const pcmk__deleted_xml_t *deleted_obj = iter->data; if (deleted_obj->position >= 0) { out->info(out, PCMK__XML_PREFIX_DELETED " %s (%d)", deleted_obj->path, deleted_obj->position); } else { out->info(out, PCMK__XML_PREFIX_DELETED " %s", deleted_obj->path); } } show_xml_changes_recursive(out, xml, 0, pcmk__xml_fmt_pretty); } // Deprecated functions kept only for backward API compatibility // LCOV_EXCL_START #include #include void log_data_element(int log_level, const char *file, const char *function, int line, const char *prefix, const xmlNode *data, int depth, int legacy_options) { uint32_t options = 0; pcmk__output_t *out = NULL; + // Confine log_level to uint8_t range + log_level = pcmk__clip_log_level(log_level); + if (data == NULL) { do_crm_log(log_level, "%s%sNo data to dump as XML", pcmk__s(prefix, ""), pcmk__str_empty(prefix)? "" : " "); return; } switch (log_level) { case LOG_NEVER: return; case LOG_STDOUT: CRM_CHECK(pcmk__text_output_new(&out, NULL) == pcmk_rc_ok, return); break; default: CRM_CHECK(pcmk__log_output_new(&out) == pcmk_rc_ok, return); pcmk__output_set_log_level(out, log_level); break; } /* Map xml_log_options to pcmk__xml_fmt_options so that we can go ahead and * start using the pcmk__xml_fmt_options in all the internal functions. * * xml_log_option_dirty_add and xml_log_option_diff_all are ignored by * internal code and only used here, so they don't need to be addressed. */ if (pcmk_is_set(legacy_options, xml_log_option_filtered)) { options |= pcmk__xml_fmt_filtered; } if (pcmk_is_set(legacy_options, xml_log_option_formatted)) { options |= pcmk__xml_fmt_pretty; } if (pcmk_is_set(legacy_options, xml_log_option_full_fledged)) { options |= pcmk__xml_fmt_full; } if (pcmk_is_set(legacy_options, xml_log_option_open)) { options |= pcmk__xml_fmt_open; } if (pcmk_is_set(legacy_options, xml_log_option_children)) { options |= pcmk__xml_fmt_children; } if (pcmk_is_set(legacy_options, xml_log_option_close)) { options |= pcmk__xml_fmt_close; } if (pcmk_is_set(legacy_options, xml_log_option_text)) { options |= pcmk__xml_fmt_text; } if (pcmk_is_set(legacy_options, xml_log_option_diff_plus)) { options |= pcmk__xml_fmt_diff_plus; } if (pcmk_is_set(legacy_options, xml_log_option_diff_minus)) { options |= pcmk__xml_fmt_diff_minus; } if (pcmk_is_set(legacy_options, xml_log_option_diff_short)) { options |= pcmk__xml_fmt_diff_short; } // Log element based on options if (pcmk_is_set(legacy_options, xml_log_option_dirty_add)) { CRM_CHECK(depth >= 0, depth = 0); show_xml_changes_recursive(out, data, depth, options); goto done; } if (pcmk_is_set(options, pcmk__xml_fmt_pretty) && (!xml_has_children(data) || (crm_element_value(data, XML_DIFF_MARKER) != NULL))) { if (pcmk_is_set(options, pcmk__xml_fmt_diff_plus)) { legacy_options |= xml_log_option_diff_all; prefix = PCMK__XML_PREFIX_CREATED; } else if (pcmk_is_set(options, pcmk__xml_fmt_diff_minus)) { legacy_options |= xml_log_option_diff_all; prefix = PCMK__XML_PREFIX_DELETED; } } if (pcmk_is_set(options, pcmk__xml_fmt_diff_short) && !pcmk_is_set(legacy_options, xml_log_option_diff_all)) { if (!pcmk_any_flags_set(options, pcmk__xml_fmt_diff_plus |pcmk__xml_fmt_diff_minus)) { // Nothing will ever be logged goto done; } // Keep looking for the actual change for (const xmlNode *child = pcmk__xml_first_child(data); child != NULL; child = pcmk__xml_next(child)) { log_data_element(log_level, file, function, line, prefix, child, depth + 1, options); } } else { pcmk__xml_show(out, prefix, data, depth, options |pcmk__xml_fmt_open |pcmk__xml_fmt_children |pcmk__xml_fmt_close); } done: out->finish(out, CRM_EX_OK, true, NULL); pcmk__output_free(out); } void xml_log_changes(uint8_t log_level, const char *function, const xmlNode *xml) { pcmk__output_t *out = NULL; switch (log_level) { case LOG_NEVER: return; case LOG_STDOUT: CRM_CHECK(pcmk__text_output_new(&out, NULL) == pcmk_rc_ok, return); break; default: CRM_CHECK(pcmk__log_output_new(&out) == pcmk_rc_ok, return); pcmk__output_set_log_level(out, log_level); break; } pcmk__xml_show_changes(out, xml); out->finish(out, CRM_EX_OK, true, NULL); pcmk__output_free(out); } // LCOV_EXCL_STOP // End deprecated API