summaryrefslogtreecommitdiffstats
path: root/lib/common/output.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/common/output.c')
-rw-r--r--lib/common/output.c318
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;
+}