diff options
Diffstat (limited to 'lib/common/output.c')
-rw-r--r-- | lib/common/output.c | 318 |
1 files changed, 318 insertions, 0 deletions
diff --git a/lib/common/output.c b/lib/common/output.c new file mode 100644 index 0000000..2ea9b0b --- /dev/null +++ b/lib/common/output.c @@ -0,0 +1,318 @@ +/* + * 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 <crm_internal.h> + +#include <crm/common/util.h> +#include <crm/common/xml.h> +#include <libxml/tree.h> + +#include "crmcommon_private.h" + +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); +} + +/*! + * \internal + * \brief Create a new \p pcmk__output_t structure + * + * This function does not register any message functions with the newly created + * object. + * + * \param[in,out] out Where to store the new output object + * \param[in] fmt_name How to format output + * \param[in] filename Where to write formatted output. This can be a + * filename (the file will be overwritten if it already + * exists), or \p NULL or \p "-" for stdout. For no + * output, pass a filename of \p "/dev/null". + * \param[in] argv List of command line arguments + * + * \return Standard Pacemaker return code + */ +int +pcmk__bare_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__output_new(pcmk__output_t **out, const char *fmt_name, + const char *filename, char **argv) +{ + int rc = pcmk__bare_output_new(out, fmt_name, filename, argv); + + if (rc == pcmk_rc_ok) { + /* Register libcrmcommon messages (currently they exist only for + * patchset) + */ + pcmk__register_patchset_messages(*out); + } + return rc; +} + +int +pcmk__register_format(GOptionGroup *group, const char *name, + 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, + const pcmk__supported_format_t *formats) +{ + if (formats == NULL) { + return; + } + 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(void) { + 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, 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 || *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] out Output object to free + * \param[out] xml If not NULL, 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; +} + +/*! + * \internal + * \brief Create a new output object using the "text" format + * + * \param[out] out Where to store newly allocated output object + * \param[in] filename Name of output destination file + * + * \return Standard Pacemaker return code + */ +int +pcmk__text_output_new(pcmk__output_t **out, const char *filename) +{ + int rc = pcmk_rc_ok; + const char* argv[] = { "", NULL }; + pcmk__supported_format_t formats[] = { + PCMK__SUPPORTED_FORMAT_TEXT, + { NULL, NULL, NULL } + }; + + pcmk__register_formats(NULL, formats); + rc = pcmk__output_new(out, "text", filename, (char **) argv); + if ((rc != pcmk_rc_ok) || (*out == NULL)) { + crm_err("Can't create text output object to internal error: %s", + pcmk_rc_str(rc)); + return rc; + } + return pcmk_rc_ok; +} |