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 <ctype.h>
13   	#include <stdarg.h>
14   	#include <stdbool.h>
15   	#include <stdint.h>
16   	#include <stdlib.h>
17   	#include <stdio.h>
18   	
19   	typedef struct {
20   	    /* gathered in log_begin_list */
21   	    GQueue/*<char*>*/ *prefixes;
22   	    uint8_t log_level;
23   	    const char *function;
24   	    const char *file;
25   	    uint32_t line;
26   	    uint32_t tags;
27   	} private_data_t;
28   	
29   	/*!
30   	 * \internal
31   	 * \brief Log a message using output object's log level and filters
32   	 *
33   	 * \param[in] priv    Output object's private_data_t
34   	 * \param[in] fmt     printf(3)-style format string
35   	 * \param[in] args... Format string arguments
36   	 */
37   	#define logger(priv, fmt, args...) do {                                     \
38   	        qb_log_from_external_source(pcmk__s((priv)->function, __func__),    \
39   	            pcmk__s((priv)->file, __FILE__), fmt, (priv)->log_level,        \
40   	            (((priv)->line == 0)? __LINE__ : (priv)->line), (priv)->tags,   \
41   	            ##args);                                                        \
42   	    } while (0);
43   	
44   	/*!
45   	 * \internal
46   	 * \brief Log a message using an explicit log level and output object's filters
47   	 *
48   	 * \param[in] priv    Output object's private_data_t
49   	 * \param[in] level   Log level
50   	 * \param[in] fmt     printf(3)-style format string
51   	 * \param[in] ap      Variadic arguments
52   	 */
53   	#define logger_va(priv, level, fmt, ap) do {                                \
54   	        qb_log_from_external_source_va(pcmk__s((priv)->function, __func__), \
55   	            pcmk__s((priv)->file, __FILE__), fmt, level,                    \
56   	            (((priv)->line == 0)? __LINE__ : (priv)->line), (priv)->tags,   \
57   	            ap);                                                            \
58   	    } while (0);
59   	
60   	static void
61   	log_subprocess_output(pcmk__output_t *out, int exit_status,
62   	                      const char *proc_stdout, const char *proc_stderr) {
63   	    /* This function intentionally left blank */
64   	}
65   	
66   	static void
67   	log_free_priv(pcmk__output_t *out) {
68   	    private_data_t *priv = NULL;
69   	
(1) Event path: Condition "out == NULL", taking false branch.
(2) Event path: Condition "out->priv == NULL", taking false branch.
70   	    if (out == NULL || out->priv == NULL) {
71   	        return;
72   	    }
73   	
74   	    priv = out->priv;
75   	
76   	    g_queue_free(priv->prefixes);
CID (unavailable; MK=a40dffd9e45b8a5bb8bfb65db94145f3) (#1 of 1): Inconsistent C union access (INCONSISTENT_UNION_ACCESS):
(3) Event assign_union_field: The union field "in" of "_pp" is written.
(4) Event inconsistent_union_field_access: In "_pp.out", the union field used: "out" is inconsistent with the field most recently stored: "in".
77   	    g_clear_pointer(&out->priv, free);
78   	}
79   	
80   	static bool
81   	log_init(pcmk__output_t *out) {
82   	    private_data_t *priv = NULL;
83   	
84   	    pcmk__assert(out != NULL);
85   	
86   	    /* If log_init was previously called on this output struct, just return. */
87   	    if (out->priv != NULL) {
88   	        return true;
89   	    }
90   	
91   	    out->priv = calloc(1, sizeof(private_data_t));
92   	    if (out->priv == NULL) {
93   	         return false;
94   	    }
95   	
96   	    priv = out->priv;
97   	
98   	    priv->prefixes = g_queue_new();
99   	    priv->log_level = LOG_INFO;
100  	
101  	    return true;
102  	}
103  	
104  	static void
105  	log_finish(pcmk__output_t *out, crm_exit_t exit_status, bool print, void **copy_dest) {
106  	    /* This function intentionally left blank */
107  	}
108  	
109  	static void
110  	log_reset(pcmk__output_t *out) {
111  	    pcmk__assert(out != NULL);
112  	
113  	    out->dest = freopen(NULL, "w", out->dest);
114  	    pcmk__assert(out->dest != NULL);
115  	
116  	    log_free_priv(out);
117  	    log_init(out);
118  	}
119  	
120  	static void
121  	log_version(pcmk__output_t *out)
122  	{
123  	    private_data_t *priv = NULL;
124  	
125  	    pcmk__assert((out != NULL) && (out->priv != NULL));
126  	    priv = out->priv;
127  	
128  	    logger(priv, "Pacemaker " PACEMAKER_VERSION);
129  	    logger(priv,
130  	           "Written by Andrew Beekhof and the Pacemaker project contributors");
131  	}
132  	
133  	G_GNUC_PRINTF(2, 3)
134  	static void
135  	log_err(pcmk__output_t *out, const char *format, ...)
136  	{
137  	    va_list ap;
138  	    private_data_t *priv = NULL;
139  	
140  	    pcmk__assert((out != NULL) && (out->priv != NULL));
141  	    priv = out->priv;
142  	
143  	    /* Error output does not get indented, to separate it from other
144  	     * potentially indented list output.
145  	     */
146  	    va_start(ap, format);
147  	    logger_va(priv, LOG_ERR, format, ap);
148  	    va_end(ap);
149  	}
150  	
151  	static void
152  	log_output_xml(pcmk__output_t *out, const char *name, const char *buf) {
153  	    xmlNodePtr node = NULL;
154  	    private_data_t *priv = NULL;
155  	
156  	    pcmk__assert((out != NULL) && (out->priv != NULL));
157  	    priv = out->priv;
158  	
159  	    node = pcmk__xe_create(NULL, name);
160  	    pcmk__xe_set_content(node, "%s", buf);
161  	    do_crm_log_xml(priv->log_level, name, node);
162  	    free(node);
163  	}
164  	
165  	G_GNUC_PRINTF(4, 5)
166  	static void
167  	log_begin_list(pcmk__output_t *out, const char *singular_noun, const char *plural_noun,
168  	               const char *format, ...) {
169  	    int len = 0;
170  	    va_list ap;
171  	    char* buffer = NULL;
172  	    private_data_t *priv = NULL;
173  	
174  	    pcmk__assert((out != NULL) && (out->priv != NULL));
175  	    priv = out->priv;
176  	
177  	    va_start(ap, format);
178  	    len = vasprintf(&buffer, format, ap);
179  	    pcmk__assert(len >= 0);
180  	    va_end(ap);
181  	
182  	    /* Don't skip empty prefixes,
183  	     * otherwise there will be mismatch
184  	     * in the log_end_list */
185  	    if(strcmp(buffer, "") == 0) {
186  	        /* nothing */
187  	    }
188  	
189  	    g_queue_push_tail(priv->prefixes, buffer);
190  	}
191  	
192  	G_GNUC_PRINTF(3, 4)
193  	static void
194  	log_list_item(pcmk__output_t *out, const char *name, const char *format, ...)
195  	{
196  	    gsize old_len = 0;
197  	    va_list ap;
198  	    private_data_t *priv = NULL;
199  	    GString *buffer = g_string_sized_new(128);
200  	
201  	    pcmk__assert((out != NULL) && (out->priv != NULL) && (format != NULL));
202  	    priv = out->priv;
203  	
204  	    // Message format: [<prefix1>[: <prefix2>...]: ]][<name>: ]<body>
205  	
206  	    for (const GList *iter = priv->prefixes->head; iter != NULL;
207  	         iter = iter->next) {
208  	
209  	        pcmk__g_strcat(buffer, (const char *) iter->data, ": ", NULL);
210  	    }
211  	
212  	    if (!pcmk__str_empty(name)) {
213  	        pcmk__g_strcat(buffer, name, ": ", NULL);
214  	    }
215  	
216  	    old_len = buffer->len;
217  	    va_start(ap, format);
218  	    g_string_append_vprintf(buffer, format, ap);
219  	    va_end(ap);
220  	
221  	    if (buffer->len > old_len) {
222  	        // Don't log a message with an empty body
223  	        logger(priv, "%s", buffer->str);
224  	    }
225  	
226  	    g_string_free(buffer, TRUE);
227  	}
228  	
229  	static void
230  	log_end_list(pcmk__output_t *out) {
231  	    private_data_t *priv = NULL;
232  	
233  	    pcmk__assert((out != NULL) && (out->priv != NULL));
234  	    priv = out->priv;
235  	
236  	    if (priv->prefixes == NULL) {
237  	      return;
238  	    }
239  	    pcmk__assert(priv->prefixes->tail != NULL);
240  	
241  	    free((char *)priv->prefixes->tail->data);
242  	    g_queue_pop_tail(priv->prefixes);
243  	}
244  	
245  	G_GNUC_PRINTF(2, 3)
246  	static int
247  	log_info(pcmk__output_t *out, const char *format, ...)
248  	{
249  	    va_list ap;
250  	    private_data_t *priv = NULL;
251  	
252  	    pcmk__assert((out != NULL) && (out->priv != NULL));
253  	    priv = out->priv;
254  	
255  	    /* Informational output does not get indented, to separate it from other
256  	     * potentially indented list output.
257  	     */
258  	    va_start(ap, format);
259  	    logger_va(priv, priv->log_level, format, ap);
260  	    va_end(ap);
261  	
262  	    return pcmk_rc_ok;
263  	}
264  	
265  	G_GNUC_PRINTF(2, 3)
266  	static int
267  	log_transient(pcmk__output_t *out, const char *format, ...)
268  	{
269  	    va_list ap;
270  	    private_data_t *priv = NULL;
271  	
272  	    pcmk__assert((out != NULL) && (out->priv != NULL));
273  	    priv = out->priv;
274  	
275  	    va_start(ap, format);
276  	    logger_va(priv, QB_MAX(priv->log_level, LOG_DEBUG), format, ap);
277  	    va_end(ap);
278  	
279  	    return pcmk_rc_ok;
280  	}
281  	
282  	static bool
283  	log_is_quiet(pcmk__output_t *out) {
284  	    return false;
285  	}
286  	
287  	static void
288  	log_spacer(pcmk__output_t *out) {
289  	    /* This function intentionally left blank */
290  	}
291  	
292  	static void
293  	log_progress(pcmk__output_t *out, bool end) {
294  	    /* This function intentionally left blank */
295  	}
296  	
297  	static void
298  	log_prompt(const char *prompt, bool echo, char **dest) {
299  	    /* This function intentionally left blank */
300  	}
301  	
302  	pcmk__output_t *
303  	pcmk__mk_log_output(char **argv) {
304  	    pcmk__output_t *retval = calloc(1, sizeof(pcmk__output_t));
305  	
306  	    if (retval == NULL) {
307  	        return NULL;
308  	    }
309  	
310  	    retval->fmt_name = "log";
311  	    retval->request = pcmk__quote_cmdline(argv);
312  	
313  	    retval->init = log_init;
314  	    retval->free_priv = log_free_priv;
315  	    retval->finish = log_finish;
316  	    retval->reset = log_reset;
317  	
318  	    retval->register_message = pcmk__register_message;
319  	    retval->message = pcmk__call_message;
320  	
321  	    retval->subprocess_output = log_subprocess_output;
322  	    retval->version = log_version;
323  	    retval->info = log_info;
324  	    retval->transient = log_transient;
325  	    retval->err = log_err;
326  	    retval->output_xml = log_output_xml;
327  	
328  	    retval->begin_list = log_begin_list;
329  	    retval->list_item = log_list_item;
330  	    retval->end_list = log_end_list;
331  	
332  	    retval->is_quiet = log_is_quiet;
333  	    retval->spacer = log_spacer;
334  	    retval->progress = log_progress;
335  	    retval->prompt = log_prompt;
336  	
337  	    return retval;
338  	}
339  	
340  	/*!
341  	 * \internal
342  	 * \brief Get the log level for a log output object
343  	 *
344  	 * This returns 0 if the output object is not of log format.
345  	 *
346  	 * \param[in] out  Output object
347  	 *
348  	 * \return Current log level for \p out
349  	 */
350  	uint8_t
351  	pcmk__output_get_log_level(const pcmk__output_t *out)
352  	{
353  	    pcmk__assert(out != NULL);
354  	
355  	    if (pcmk__str_eq(out->fmt_name, "log", pcmk__str_none)) {
356  	        private_data_t *priv = out->priv;
357  	
358  	        pcmk__assert(priv != NULL);
359  	        return priv->log_level;
360  	    }
361  	    return 0;
362  	}
363  	
364  	/*!
365  	 * \internal
366  	 * \brief Set the log level for a log output object
367  	 *
368  	 * This does nothing if the output object is not of log format.
369  	 *
370  	 * \param[in,out] out        Output object
371  	 * \param[in]     log_level  Log level constant (\c LOG_ERR, etc.) to use
372  	 *
373  	 * \note \c LOG_INFO is used by default for new \c pcmk__output_t objects.
374  	 * \note Almost all formatted output messages respect this setting. However,
375  	 *       <tt>out->err</tt> always logs at \c LOG_ERR.
376  	 */
377  	void
378  	pcmk__output_set_log_level(pcmk__output_t *out, uint8_t log_level)
379  	{
380  	    pcmk__assert(out != NULL);
381  	
382  	    if (pcmk__str_eq(out->fmt_name, "log", pcmk__str_none)) {
383  	        private_data_t *priv = out->priv;
384  	
385  	        pcmk__assert(priv != NULL);
386  	        priv->log_level = log_level;
387  	    }
388  	}
389  	
390  	/*!
391  	 * \internal
392  	 * \brief Set the file, function, line, and tags used to filter log output
393  	 *
394  	 * This does nothing if the output object is not of log format.
395  	 *
396  	 * \param[in,out] out       Output object
397  	 * \param[in]     file      File name to filter with (or NULL for default)
398  	 * \param[in]     function  Function name to filter with (or NULL for default)
399  	 * \param[in]     line      Line number to filter with (or 0 for default)
400  	 * \param[in]     tags      Tags to filter with (or 0 for none)
401  	 *
402  	 * \note Custom filters should generally be used only in short areas of a single
403  	 *       function. When done, callers should call this function again with
404  	 *       NULL/0 arguments to reset the filters.
405  	 */
406  	void
407  	pcmk__output_set_log_filter(pcmk__output_t *out, const char *file,
408  	                            const char *function, uint32_t line, uint32_t tags)
409  	{
410  	    pcmk__assert(out != NULL);
411  	
412  	    if (pcmk__str_eq(out->fmt_name, "log", pcmk__str_none)) {
413  	        private_data_t *priv = out->priv;
414  	
415  	        pcmk__assert(priv != NULL);
416  	        priv->file = file;
417  	        priv->function = function;
418  	        priv->line = line;
419  	        priv->tags = tags;
420  	    }
421  	}
422