1    	/*
2    	 * Copyright 2019-2026 the Pacemaker project contributors
3    	 *
4    	 * The version control history for this file may have further details.
5    	 *
6    	 * This source code is licensed under the GNU Lesser General Public License
7    	 * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
8    	 */
9    	
10   	#include <crm_internal.h>
11   	
12   	#include <stdbool.h>
13   	#include <glib.h>
14   	
15   	#include <crm/common/util.h>
16   	#include <crm/common/xml.h>
17   	#include <libxml/tree.h>
18   	
19   	#include "crmcommon_private.h"
20   	
21   	static GHashTable *formatters = NULL;
22   	
23   	#if defined(PCMK__UNIT_TESTING)
24   	// LCOV_EXCL_START
25   	GHashTable *
26   	pcmk__output_formatters(void) {
27   	    return formatters;
28   	}
29   	
30   	void
31   	pcmk__set_output_formatters(GHashTable *value)
32   	{
33   	    formatters = value;
34   	}
35   	// LCOV_EXCL_STOP
36   	#endif
37   	
38   	void
39   	pcmk__output_free(pcmk__output_t *out) {
40   	    if (out == NULL) {
41   	        return;
42   	    }
43   	
44   	    out->free_priv(out);
45   	
46   	    g_clear_pointer(&out->messages, g_hash_table_destroy);
47   	    g_free(out->request);
48   	    free(out);
49   	}
50   	
51   	/*!
52   	 * \internal
53   	 * \brief Create a new \p pcmk__output_t structure
54   	 *
55   	 * This function does not register any message functions with the newly created
56   	 * object.
57   	 *
58   	 * \param[in,out] out       Where to store the new output object
59   	 * \param[in]     fmt_name  How to format output
60   	 * \param[in]     filename  Where to write formatted output. This can be a
61   	 *                          filename (the file will be overwritten if it already
62   	 *                          exists), or \p NULL or \p "-" for stdout. For no
63   	 *                          output, pass a filename of \p "/dev/null".
64   	 * \param[in]     argv      List of command line arguments
65   	 *
66   	 * \return Standard Pacemaker return code
67   	 */
68   	int
69   	pcmk__bare_output_new(pcmk__output_t **out, const char *fmt_name,
70   	                      const char *filename, char **argv)
71   	{
72   	    pcmk__output_factory_t create = NULL;
73   	
74   	    pcmk__assert((formatters != NULL) && (out != NULL));
75   	
76   	    /* If no name was given, just try "text".  It's up to each tool to register
77   	     * what it supports so this also may not be valid.
78   	     */
79   	    if (fmt_name == NULL) {
80   	        create = g_hash_table_lookup(formatters, "text");
81   	    } else {
82   	        create = g_hash_table_lookup(formatters, fmt_name);
83   	    }
84   	
85   	    if (create == NULL) {
86   	        return pcmk_rc_unknown_format;
87   	    }
88   	
89   	    *out = create(argv);
90   	    if (*out == NULL) {
91   	        return ENOMEM;
92   	    }
93   	
94   	    /* Static analysis can't figure out that passing pcmk__str_null_matches
95   	     * to pcmk__str_eq means filename can't be NULL in the else block, so
96   	     * we'll just take care of that here.
97   	     */
98   	    if (filename == NULL) {
99   	        filename = "-";
100  	    }
101  	
102  	    if (pcmk__str_eq(filename, "-", pcmk__str_none)) {
103  	        (*out)->dest = stdout;
104  	    } else {
105  	        (*out)->dest = fopen(filename, "w");
106  	        if ((*out)->dest == NULL) {
107  	            int rc = errno;
108  	
109  	            g_clear_pointer(out, pcmk__output_free);
110  	            return rc;
111  	        }
112  	    }
113  	
114  	    (*out)->quiet = false;
115  	    (*out)->messages = pcmk__strkey_table(free, NULL);
116  	
117  	    if ((*out)->init(*out) == false) {
118  	        g_clear_pointer(out, pcmk__output_free);
119  	        return ENOMEM;
120  	    }
121  	
122  	    setenv("OCF_OUTPUT_FORMAT", (*out)->fmt_name, 1);
123  	
124  	    return pcmk_rc_ok;
125  	}
126  	
127  	int
128  	pcmk__output_new(pcmk__output_t **out, const char *fmt_name,
129  	                 const char *filename, char **argv)
130  	{
131  	    int rc = pcmk__bare_output_new(out, fmt_name, filename, argv);
132  	
133  	    if (rc == pcmk_rc_ok) {
134  	        // Register libcrmcommon messages
135  	        pcmk__register_option_messages(*out);
136  	        pcmk__register_patchset_messages(*out);
137  	    }
138  	    return rc;
139  	}
140  	
141  	int
142  	pcmk__register_format(GOptionGroup *group, const char *name,
143  	                      pcmk__output_factory_t create,
144  	                      const GOptionEntry *options)
145  	{
146  	    char *name_copy = NULL;
147  	
148  	    pcmk__assert((create != NULL) && !pcmk__str_empty(name));
149  	
150  	    // cppcheck doesn't understand the above pcmk__assert line
151  	    // cppcheck-suppress ctunullpointer
152  	    name_copy = strdup(name);
153  	    if (name_copy == NULL) {
154  	        return ENOMEM;
155  	    }
156  	
157  	    if (formatters == NULL) {
158  	        formatters = pcmk__strkey_table(free, NULL);
159  	    }
160  	
161  	    if (options != NULL && group != NULL) {
162  	        g_option_group_add_entries(group, options);
163  	    }
164  	
165  	    g_hash_table_insert(formatters, name_copy, create);
166  	    return pcmk_rc_ok;
167  	}
168  	
169  	void
170  	pcmk__register_formats(GOptionGroup *group,
171  	                       const pcmk__supported_format_t *formats)
172  	{
173  	    if (formats == NULL) {
174  	        return;
175  	    }
176  	    for (const pcmk__supported_format_t *entry = formats; entry->name != NULL;
177  	         entry++) {
178  	        pcmk__register_format(group, entry->name, entry->create, entry->options);
179  	    }
180  	}
181  	
182  	void
183  	pcmk__unregister_formats(void)
184  	{
CID (unavailable; MK=948a8c18a88cfd7b57a024d00914bb46) (#1 of 1): Inconsistent C union access (INCONSISTENT_UNION_ACCESS):
(1) Event assign_union_field: The union field "in" of "_pp" is written.
(2) Event inconsistent_union_field_access: In "_pp.out", the union field used: "out" is inconsistent with the field most recently stored: "in".
185  	    g_clear_pointer(&formatters, g_hash_table_destroy);
186  	}
187  	
188  	int
189  	pcmk__call_message(pcmk__output_t *out, const char *message_id, ...) {
190  	    va_list args;
191  	    int rc = pcmk_rc_ok;
192  	    pcmk__message_fn_t fn;
193  	
194  	    pcmk__assert((out != NULL) && !pcmk__str_empty(message_id));
195  	
196  	    fn = g_hash_table_lookup(out->messages, message_id);
197  	    if (fn == NULL) {
198  	        pcmk__debug("Called unknown output message '%s' for format '%s'",
199  	                    message_id, out->fmt_name);
200  	        return EINVAL;
201  	    }
202  	
203  	    va_start(args, message_id);
204  	    rc = fn(out, args);
205  	    va_end(args);
206  	
207  	    return rc;
208  	}
209  	
210  	void
211  	pcmk__register_message(pcmk__output_t *out, const char *message_id,
212  	                       pcmk__message_fn_t fn)
213  	{
214  	    pcmk__assert((out != NULL) && !pcmk__str_empty(message_id) && (fn != NULL));
215  	    g_hash_table_replace(out->messages, pcmk__str_copy(message_id), fn);
216  	}
217  	
218  	void
219  	pcmk__register_messages(pcmk__output_t *out, const pcmk__message_entry_t *table)
220  	{
221  	    for (const pcmk__message_entry_t *entry = table; entry->message_id != NULL;
222  	         entry++) {
223  	        if (pcmk__strcase_any_of(entry->fmt_name, "default", out->fmt_name, NULL)) {
224  	            pcmk__register_message(out, entry->message_id, entry->fn);
225  	        }
226  	    }
227  	}
228  	
229  	void
230  	pcmk__output_and_clear_error(GError **error, pcmk__output_t *out)
231  	{
232  	    if (error == NULL || *error == NULL) {
233  	        return;
234  	    }
235  	
236  	    if (out != NULL) {
237  	        out->err(out, "%s: %s", g_get_prgname(), (*error)->message);
238  	    } else {
239  	        fprintf(stderr, "%s: %s\n", g_get_prgname(), (*error)->message);
240  	    }
241  	
242  	    g_clear_error(error);
243  	}
244  	
245  	/*!
246  	 * \internal
247  	 * \brief Create an XML-only output object
248  	 *
249  	 * Create an output object that supports only the XML format, and free
250  	 * existing XML if supplied (particularly useful for libpacemaker public API
251  	 * functions that want to free any previous result supplied by the caller).
252  	 *
253  	 * \param[out]     out  Where to put newly created output object
254  	 * \param[in,out]  xml  If \c *xml is non-NULL, this will be freed
255  	 *
256  	 * \return Standard Pacemaker return code
257  	 */
258  	int
259  	pcmk__xml_output_new(pcmk__output_t **out, xmlNodePtr *xml) {
260  	    pcmk__supported_format_t xml_format[] = {
261  	        PCMK__SUPPORTED_FORMAT_XML,
262  	        { NULL, NULL, NULL }
263  	    };
264  	
265  	    if (xml == NULL) {
266  	        return EINVAL;
267  	    }
268  	
269  	    g_clear_pointer(xml, pcmk__xml_free);
270  	    pcmk__register_formats(NULL, xml_format);
271  	    return pcmk__output_new(out, "xml", NULL, NULL);
272  	}
273  	
274  	/*!
275  	 * \internal
276  	 * \brief  Finish and free an XML-only output object
277  	 *
278  	 * \param[in,out] out         Output object to free
279  	 * \param[in]     exit_status The exit value of the whole program
280  	 * \param[out]    xml         If not NULL, where to store XML output
281  	 */
282  	void
283  	pcmk__xml_output_finish(pcmk__output_t *out, crm_exit_t exit_status,
284  	                        xmlNodePtr *xml)
285  	{
286  	    if (out == NULL) {
287  	        return;
288  	    }
289  	
290  	    out->finish(out, exit_status, FALSE, (void **) xml);
291  	    pcmk__output_free(out);
292  	}
293  	
294  	/*!
295  	 * \internal
296  	 * \brief Create a new output object using the "log" format
297  	 *
298  	 * \param[out] out  Where to store newly allocated output object
299  	 *
300  	 * \return Standard Pacemaker return code
301  	 */
302  	int
303  	pcmk__log_output_new(pcmk__output_t **out)
304  	{
305  	    int rc = pcmk_rc_ok;
306  	    const char* argv[] = { "", NULL };
307  	    pcmk__supported_format_t formats[] = {
308  	        PCMK__SUPPORTED_FORMAT_LOG,
309  	        { NULL, NULL, NULL }
310  	    };
311  	
312  	    pcmk__register_formats(NULL, formats);
313  	    rc = pcmk__output_new(out, "log", NULL, (char **) argv);
314  	    if ((rc != pcmk_rc_ok) || (*out == NULL)) {
315  	        pcmk__err("Can't log certain messages due to internal error: %s",
316  	                  pcmk_rc_str(rc));
317  	        return rc;
318  	    }
319  	    return pcmk_rc_ok;
320  	}
321  	
322  	/*!
323  	 * \internal
324  	 * \brief Create a new output object using the "text" format
325  	 *
326  	 * \param[out] out       Where to store newly allocated output object
327  	 * \param[in]  filename  Name of output destination file
328  	 *
329  	 * \return Standard Pacemaker return code
330  	 */
331  	int
332  	pcmk__text_output_new(pcmk__output_t **out, const char *filename)
333  	{
334  	    int rc = pcmk_rc_ok;
335  	    const char* argv[] = { "", NULL };
336  	    pcmk__supported_format_t formats[] = {
337  	        PCMK__SUPPORTED_FORMAT_TEXT,
338  	        { NULL, NULL, NULL }
339  	    };
340  	
341  	    pcmk__register_formats(NULL, formats);
342  	    rc = pcmk__output_new(out, "text", filename, (char **) argv);
343  	    if ((rc != pcmk_rc_ok) || (*out == NULL)) {
344  	        pcmk__err("Can't create text output object to internal error: %s",
345  	                  pcmk_rc_str(rc));
346  	        return rc;
347  	    }
348  	    return pcmk_rc_ok;
349  	}
350