1    	/*
2    	 * Copyright 2004-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 <errno.h>                  // errno
13   	#include <fcntl.h>                  // open, O_CREAT, O_RDWR, S_*
14   	#include <libgen.h>                 // basename
15   	#include <signal.h>                 // raise, SIG*
16   	#include <stdbool.h>
17   	#include <stddef.h>                 // NULL
18   	#include <stdint.h>                 // int32_t, uint8_t, uint32_t
19   	#include <stdlib.h>                 // free, getenv, strtol
20   	#include <string.h>                 // strdup, strerror, strstr
21   	#include <syslog.h>                 // LOG_*, syslog
22   	#include <sys/stat.h>               // S_*, stat
23   	#include <sys/types.h>              // gid_t, mode_t, pid_t, uid_t
24   	#include <sys/utsname.h>            // uname, utsname
25   	#include <time.h>                   // time, time_t, timespec
26   	#include <unistd.h>                 // chdir, close, fchown, geteuid, getpid
27   	
28   	#include <glib.h>                   // g_*, G_*, gboolean, gchar, etc.
29   	#include <libxml/tree.h>            // xmlNode
30   	#include <qb/qbdefs.h>              // qb_bit_set, QB_FALSE, QB_TRUE
31   	#include <qb/qblog.h>               // LOG_TRACE, qb_log_*
32   	
33   	#include <crm_config.h>             // CRM_DAEMON_USER, CRM_*_DIR
34   	#include <crm/common/internal.h>    // pcmk__env_*, pcmk__output_*, etc.
35   	#include <crm/common/logging.h>     // do_crm_log, CRM_CHECK, etc.
36   	#include <crm/common/mainloop.h>    // crm_signal_handler, mainloop_add_signal
37   	#include <crm/common/options.h>     // PCMK_VALUE_NONE
38   	#include <crm/common/results.h>     // crm_abort, pcmk_rc_*
39   	#include <crm/crm.h>                // crm_system_name
40   	
41   	#include "crmcommon_private.h"      // pcmk__add_logfile
42   	
43   	// Use high-resolution (millisecond) timestamps if libqb supports them
44   	#ifdef QB_FEATURE_LOG_HIRES_TIMESTAMPS
45   	#define TIMESTAMP_FORMAT_SPEC "%%T"
46   	typedef struct timespec *log_time_t;
47   	#else
48   	#define TIMESTAMP_FORMAT_SPEC "%%t"
49   	typedef time_t log_time_t;
50   	#endif
51   	
52   	unsigned int crm_log_level = LOG_INFO;
53   	unsigned int crm_trace_nonlog = 0;
54   	bool pcmk__is_daemon = false;
55   	
56   	static int blackbox_trigger = 0;
57   	static char *blackbox_file_prefix = NULL;
58   	
59   	static gchar **trace_blackbox = NULL;
60   	static gchar **trace_files = NULL;
61   	static gchar **trace_formats = NULL;
62   	static gchar **trace_functions = NULL;
63   	static bool tracing_enabled = false;
64   	
65   	static unsigned int crm_log_priority = LOG_NOTICE;
66   	static pcmk__output_t *logger_out = NULL;
67   	
68   	pcmk__config_error_func pcmk__config_error_handler = NULL;
69   	pcmk__config_warning_func pcmk__config_warning_handler = NULL;
70   	void *pcmk__config_error_context = NULL;
71   	void *pcmk__config_warning_context = NULL;
72   	
73   	/*!
74   	 * \internal
75   	 * \brief Mapping of a GLib log domain string to its log handler ID
76   	 */
77   	struct log_handler_id {
78   	    //! Log domain
79   	    const char *log_domain;
80   	
81   	    /*!
82   	     * Log handler function ID. GLib does not specify the meaning of 0, but
83   	     * based on the implementation, a valid ID is always positive unless we set
84   	     * UINT_MAX handlers.
85   	     */
86   	    guint handler_id;
87   	};
88   	
89   	// Log domains that we care about, and their handler function IDs once set
90   	static struct log_handler_id log_handler_ids[] = {
91   	    { G_LOG_DOMAIN, 0 },
92   	    { "GLib", 0 },
93   	    { "GLib-GIO", 0 },
94   	    { "GModule", 0 },
95   	    { "GThread", 0 },
96   	};
97   	
98   	/*!
99   	 * \internal
100  	 * \brief Convert a GLib log level to a syslog log level
101  	 *
102  	 * \param[in] log_level  GLib log level
103  	 *
104  	 * \return The syslog level corresponding to \p log_level
105  	 */
106  	static uint8_t
107  	log_level_from_glib(GLogLevelFlags log_level)
108  	{
109  	    switch (log_level & G_LOG_LEVEL_MASK) {
110  	        case G_LOG_LEVEL_CRITICAL:
111  	            return LOG_CRIT;
112  	
113  	        case G_LOG_LEVEL_ERROR:
114  	            return LOG_ERR;
115  	
116  	        case G_LOG_LEVEL_MESSAGE:
117  	            return LOG_NOTICE;
118  	
119  	        case G_LOG_LEVEL_INFO:
120  	            return LOG_INFO;
121  	
122  	        case G_LOG_LEVEL_DEBUG:
123  	            return LOG_DEBUG;
124  	
125  	        case G_LOG_LEVEL_WARNING:
126  	            return LOG_WARNING;
127  	
128  	        default:
129  	            // Default to LOG_NOTICE for any new or custom GLib log levels
130  	            return LOG_NOTICE;
131  	    }
132  	}
133  	
134  	/*!
135  	 * \internal
136  	 * \brief Handle a log message from GLib
137  	 *
138  	 * \param[in] log_domain  Log domain of the message
139  	 * \param[in] log_level   Log level of the message (including fatal and
140  	 *                        recursion flags)
141  	 * \param[in] message     Message to process
142  	 * \param[in] user_data   Ignored
143  	 */
144  	static void
145  	handle_glib_message(const gchar *log_domain, GLogLevelFlags log_level,
146  	                    const gchar *message, gpointer user_data)
147  	
148  	{
149  	    uint8_t syslog_level = log_level_from_glib(log_level);
150  	
151  	    if (syslog_level == LOG_CRIT) {
152  	        static struct qb_log_callsite *glib_cs = NULL;
153  	
154  	        if (glib_cs == NULL) {
155  	            glib_cs = qb_log_callsite_get(__func__, __FILE__, "glib-handler",
156  	                                          LOG_DEBUG, __LINE__,
157  	                                          crm_trace_nonlog);
158  	        }
159  	
160  	        if (!crm_is_callsite_active(glib_cs, LOG_DEBUG, crm_trace_nonlog)) {
161  	            // Dump core
162  	            crm_abort(__FILE__, __func__, __LINE__, message, true, true);
163  	        }
164  	    }
165  	
166  	    do_crm_log(syslog_level, "%s: %s", log_domain, message);
167  	}
168  	
169  	/*!
170  	 * \internal
171  	 * \brief Set \c handle_glib_message() as the handler for each GLib log domain
172  	 *
173  	 * The handler will be set for all log levels, including fatal and recursive
174  	 * messages, for each GLib log domain that we care about.
175  	 */
176  	static void
177  	set_glib_log_handlers(void)
178  	{
179  	    for (int i = 0; i < PCMK__NELEM(log_handler_ids); i++) {
180  	        struct log_handler_id *entry = &log_handler_ids[i];
181  	
182  	        entry->handler_id = g_log_set_handler(entry->log_domain,
183  	                                              G_LOG_LEVEL_MASK
184  	                                              |G_LOG_FLAG_FATAL
185  	                                              |G_LOG_FLAG_RECURSION,
186  	                                              handle_glib_message, NULL);
187  	    }
188  	}
189  	
190  	/*!
191  	 * \internal
192  	 * \brief Remove the handler for each GLib log domain that we care about
193  	 */
194  	static void
195  	remove_glib_log_handlers(void)
196  	{
197  	    for (int i = 0; i < PCMK__NELEM(log_handler_ids); i++) {
198  	        struct log_handler_id *entry = &log_handler_ids[i];
199  	
200  	        if (entry->handler_id == 0) {
201  	            continue;
202  	        }
203  	
204  	        g_log_remove_handler(entry->log_domain, entry->handler_id);
205  	        entry->handler_id = 0;
206  	    }
207  	}
208  	
209  	/*!
210  	 * \internal
211  	 * \brief Set the log format string based on the given target
212  	 *
213  	 * \param[in] target     Log target (detail level)
214  	 * \param[in] pid        PID of current process
215  	 * \param[in] node_name  Local node's name (for use in log output)
216  	 */
217  	static void
218  	set_format_string(int target, pid_t pid, const char *node_name)
219  	{
220  	    /* Format specifiers used here:
221  	     * - %b: message
222  	     * - %f: file name
223  	     * - %g: tag, or "" if none
224  	     * - %l: line number
225  	     * - %n: function name
226  	     * - %p: priority (for example, "error" or "debug")
227  	     * - %t: timestamp at seconds resolution
228  	     * - %T: timestamp at milliseconds resolution
229  	     */
230  	    GString *fmt = NULL;
231  	
232  	    if (target == QB_LOG_SYSLOG) {
233  	        // The system log gets a simplified, user-friendly format
234  	        qb_log_ctl(target, QB_LOG_CONF_EXTENDED, QB_FALSE);
235  	        qb_log_format_set(target, "%g %p: %b");
236  	        return;
237  	    }
238  	
239  	    // Everything else gets more detail, for advanced troubleshooting
240  	    fmt = g_string_sized_new(256);
241  	
242  	    if (target > QB_LOG_STDERR) {
243  	        // If logging to a file, prefix with timestamp, node, system, and PID
244  	        g_string_append_printf(fmt, TIMESTAMP_FORMAT_SPEC " %s %-20s[%lld] ",
245  	                               node_name, crm_system_name, (long long) pid);
246  	    }
247  	
248  	    // Add function name (in parentheses)
249  	    g_string_append(fmt, "(%n");
250  	
251  	    if (tracing_enabled) {
252  	        // When tracing, add file and line number
253  	        g_string_append(fmt, "@%f:%l");
254  	    }
255  	
256  	    g_string_append_c(fmt, ')');
257  	
258  	    // Add tag (if any), priority, and actual message
259  	    g_string_append(fmt, " %g\t%p: %b");
260  	
261  	    qb_log_format_set(target, fmt->str);
262  	    g_string_free(fmt, TRUE);
263  	}
264  	
265  	#define DEFAULT_LOG_FILE CRM_LOG_DIR "/pacemaker.log"
266  	
267  	/*!
268  	 * \internal
269  	 * \brief Ensure the daemon group owns the log file or has read/write access
270  	 *
271  	 * If the daemon group (\c haclient by default) doesn't own the log file or
272  	 * can't read and write it, change the log file's ownership to the daemon user
273  	 * and group.
274  	 *
275  	 * \param[in] filename  Log file name (for logging only)
276  	 * \param[in] logfd     Log file descriptor
277  	 *
278  	 * \return Standard Pacemaker return code
279  	 *
280  	 * \todo Should we be less aggressive in changing ownership? The daemon group
281  	 *       may have read/write permissions via file ACLs, or the daemon user may
282  	 *       have permissions via user ownership, secondary group membership, or
283  	 *       file ACLs. Also, even after changing group ownership, the daemon group
284  	 *       may not have read/write permissions. Even if it does, we may unset them
285  	 *       when applying PCMK__ENV_LOGFILE_MODE in \c chmod_logfile().
286  	 */
287  	static int
288  	chown_logfile(const char *filename, int logfd)
289  	{
290  	    uid_t pcmk_uid = 0;
291  	    gid_t pcmk_gid = 0;
292  	    struct stat st;
293  	    int rc = pcmk_rc_ok;
294  	
295  	    // Get the log file's current ownership and permissions
296  	    if (fstat(logfd, &st) < 0) {
297  	        rc = errno;
298  	        pcmk__warn("Logging to '%s' is disabled because fstat() failed: %s",
299  	                   filename, strerror(rc));
300  	        return rc;
301  	    }
302  	
303  	    // Any other errors don't prevent file from being used as log
304  	
305  	    rc = pcmk__daemon_user(&pcmk_uid, &pcmk_gid);
306  	    if (rc != pcmk_rc_ok) {
307  	        pcmk__warn("Not changing '%s' ownership because user information "
308  	                   "unavailable: %s",
309  	                   filename, pcmk_rc_str(rc));
310  	        return pcmk_rc_ok;
311  	    }
312  	
313  	    if ((st.st_gid == pcmk_gid)
314  	        && ((st.st_mode & S_IRWXG) == (S_IRGRP|S_IWGRP))) {
315  	
316  	        // Daemon group already owns the log file and has read/write permissions
317  	        return pcmk_rc_ok;
318  	    }
319  	
320  	    if (fchown(logfd, pcmk_uid, pcmk_gid) < 0) {
321  	        pcmk__warn("Couldn't change '%s' ownership to user %s gid %d: %s",
322  	                   filename, CRM_DAEMON_USER, pcmk_gid, strerror(errno));
323  	    }
324  	
325  	    return pcmk_rc_ok;
326  	}
327  	
328  	/*!
329  	 * \internal
330  	 * \brief Set log file permissions using environment variable or default value
331  	 *
332  	 * If the \c PCMK__ENV_LOGFILE_MODE environment variable is set to a valid
333  	 * value, this function sets the log file mode to that value. Otherwise, it sets
334  	 * the log file mode to 0660.
335  	 *
336  	 * An error causes a warning to be logged but is otherwise ignored.
337  	 *
338  	 * \param[in] filename  Log file name (for logging only)
339  	 * \param[in] logfd     Log file descriptor
340  	 */
341  	static void
342  	chmod_logfile(const char *filename, int logfd)
343  	{
344  	    const char *modestr = pcmk__env_option(PCMK__ENV_LOGFILE_MODE);
345  	    mode_t filemode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP;
346  	
347  	    errno = 0;
348  	    if (modestr != NULL) {
349  	        char *end = NULL;
350  	        const long filemode_l = strtol(modestr, &end, 8);
351  	        const mode_t mask = S_ISUID|S_ISGID|S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO;
352  	
353  	        if ((errno != 0) || (end == modestr) || !pcmk__str_empty(end)
354  	            || (((mode_t) filemode_l & ~mask) != 0)) {
355  	
356  	            pcmk__warn("Setting '%s' mode to %04o because "
357  	                       "PCMK_" PCMK__ENV_LOGFILE_MODE " '%s' is invalid",
358  	                       filename, filemode, modestr);
359  	
360  	        } else {
361  	            filemode = (mode_t) filemode_l;
362  	        }
363  	    }
364  	
365  	    if (fchmod(logfd, filemode) < 0) {
366  	        pcmk__warn("Couldn't change '%s' mode to %04o: %s", filename, filemode,
367  	                   strerror(errno));
368  	    }
369  	}
370  	
371  	/*!
372  	 * \internal
373  	 * \brief If we're root, ensure a log file has correct permissions and ownership
374  	 *
375  	 * \param[in] filename  Log file name
376  	 *
377  	 * \return Standard Pacemaker return code
378  	 */
379  	static int
380  	set_logfile_permissions(const char *filename)
381  	{
382  	    int fd = 0;
383  	    int rc = pcmk_rc_ok;
384  	
385  	    if (geteuid() != 0) {
386  	        goto done;
387  	    }
388  	
389  	    fd = open(filename, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
390  	    if (fd < 0) {
391  	        rc = errno;
392  	        pcmk__warn("Logging to '%s' is disabled because open() failed as root: "
393  	                   "%s", filename, strerror(rc));
394  	        goto done;
395  	    }
396  	
397  	    rc = chown_logfile(filename, fd);
398  	    if (rc != pcmk_rc_ok) {
399  	        goto done;
400  	    }
401  	
402  	    chmod_logfile(filename, fd);
403  	
404  	done:
405  	    if (fd >= 0) {
406  	        close(fd);
407  	    }
408  	    return rc;
409  	}
410  	
411  	/*!
412  	 * \internal
413  	 * \brief Enable a libqb log target, extend max line length, and apply filter
414  	 *
415  	 * \param[in] target  Log target (either an <tt>enum qb_log_target_slot</tt> or
416  	 *                    the return value of a \c qb_log_file_open() call)
417  	 */
418  	static void
419  	enable_logfile(int32_t target)
420  	{
421  	    qb_log_ctl(target, QB_LOG_CONF_ENABLED, QB_TRUE);
422  	
423  	#ifdef HAVE_qb_log_conf_QB_LOG_CONF_MAX_LINE_LEN
424  	    // Longer than default, for logging long XML lines
425  	    qb_log_ctl(target, QB_LOG_CONF_MAX_LINE_LEN, 800);
426  	#endif
427  	
428  	    crm_update_callsites();
429  	}
430  	
431  	/*!
432  	 * \internal
433  	 * \brief Disable a libqb log target
434  	 *
435  	 * \param[in] target  Log target (either an <tt>enum qb_log_target_slot</tt> or
436  	 *                    the return value of a \c qb_log_file_open() call)
437  	 */
438  	static inline void
439  	disable_logfile(int32_t target)
440  	{
441  	    qb_log_ctl(target, QB_LOG_CONF_ENABLED, QB_FALSE);
442  	}
443  	
444  	static void
445  	setenv_logfile(const char *filename)
446  	{
447  	    // Some resource agents will log only if environment variable is set
448  	    if (pcmk__env_option(PCMK__ENV_LOGFILE) == NULL) {
449  	        pcmk__set_env_option(PCMK__ENV_LOGFILE, filename, true);
450  	    }
451  	}
452  	
453  	/*!
454  	 * \internal
455  	 * \brief Add a file to be used as a Pacemaker detail log
456  	 *
457  	 * \param[in] filename  Name of log file to use. \c NULL adds the default log
458  	 *                      file if no log file is enabled yet. \c PCMK_VALUE_NONE
459  	 *                      or \c "/dev/null" causes this function to do nothing.
460  	 *
461  	 * \return Standard Pacemaker return code
462  	 *
463  	 * \note Messages from this function won't be logged to the new log file.
464  	 */
465  	int
466  	pcmk__add_logfile(const char *filename)
467  	{
468  	    static int32_t default_target = -1;
469  	    static bool have_logfile = false;
470  	
471  	    int32_t target = 0;
472  	    int rc = pcmk_rc_ok;
473  	    bool is_default = false;
474  	
475  	    // No logging to do if the file is "/dev/null" or special value "none"
476  	    if (pcmk__str_eq(filename, "/dev/null", pcmk__str_none)
477  	        || pcmk__str_eq(filename, PCMK_VALUE_NONE, pcmk__str_casei)) {
478  	
479  	        return pcmk_rc_ok;
480  	    }
481  	
482  	    // Use default if caller didn't specify (and we don't already have one)
483  	    if (filename == NULL) {
484  	        if (have_logfile) {
485  	            return pcmk_rc_ok;
486  	        }
487  	        filename = DEFAULT_LOG_FILE;
488  	    }
489  	
490  	    // If the caller wants the default and we already have it, we're done
491  	    is_default = pcmk__str_eq(filename, DEFAULT_LOG_FILE, pcmk__str_none);
492  	    if (is_default && (default_target >= 0)) {
493  	        return pcmk_rc_ok;
494  	    }
495  	
496  	    // If we're root, ensure ownership and permissions are correct
497  	    rc = set_logfile_permissions(filename);
498  	    if (rc != pcmk_rc_ok) {
499  	        // Warning has already been logged
500  	        return rc;
501  	    }
502  	
503  	    // Open as a libqb log file
504  	    target = qb_log_file_open(filename);
505  	    if (target < 0) {
506  	        rc = -target;   // target == -errno
507  	        pcmk__warn("Logging to '%s' is disabled because qb_log_file_open() "
508  	                   "failed: %s " QB_XS " uid=%lld gid=%lld",
509  	                   filename, strerror(rc), (long long) geteuid(),
510  	                   (long long) getegid());
511  	        return rc;
512  	    }
513  	
514  	    if (is_default) {
515  	        default_target = target;
516  	        setenv_logfile(filename);
517  	
518  	    } else if (default_target >= 0) {
519  	        /* @TODO Why do we disable logging to the default log file when adding
520  	         * another log file? This seems wrong, especially if the default file is
521  	         * configured explicitly.
522  	         */
523  	        pcmk__notice("Switching logging to %s", filename);
524  	        disable_logfile(default_target);
525  	    }
526  	
527  	    pcmk__notice("Additional logging available in %s", filename);
528  	    enable_logfile(target);
529  	    have_logfile = true;
530  	    return pcmk_rc_ok;
531  	}
532  	
533  	/*!
534  	 * \brief Add multiple additional log files
535  	 *
536  	 * \param[in] log_files  Array of log files to add
537  	 * \param[in] out        Output object to use for error reporting
538  	 *
539  	 * \return Standard Pacemaker return code
540  	 */
541  	void
542  	pcmk__add_logfiles(gchar **log_files, pcmk__output_t *out)
543  	{
544  	    if (log_files == NULL) {
545  	        return;
546  	    }
547  	
548  	    for (gchar **fname = log_files; *fname != NULL; fname++) {
549  	        int rc = pcmk__add_logfile(*fname);
550  	
551  	        if (rc != pcmk_rc_ok) {
552  	            out->err(out, "Logging to %s is disabled: %s",
553  	                     *fname, pcmk_rc_str(rc));
554  	        }
555  	    }
556  	}
557  	
558  	/*!
559  	 * \internal
560  	 * \brief Write out a blackbox (enabling blackboxes if the signal is \c SIGTRAP)
561  	 *
562  	 * \param[in] nsig  Signal number that was received
563  	 *
564  	 * \note This is a true signal handler, and so must be async-safe.
565  	 */
566  	static void
567  	enable_and_write_blackbox(int nsig)
568  	{
569  	    if (nsig == SIGTRAP) {
570  	        crm_enable_blackbox(nsig);
571  	    }
572  	
573  	    crm_write_blackbox(nsig, NULL);
574  	}
575  	
576  	static void
577  	blackbox_logger(int32_t t, struct qb_log_callsite *cs, log_time_t timestamp,
578  	                const char *msg)
579  	{
580  	    if(cs && cs->priority < LOG_ERR) {
581  	        crm_write_blackbox(SIGTRAP, cs); /* Bypass the over-dumping logic */
582  	    } else {
583  	        crm_write_blackbox(0, cs);
584  	    }
585  	}
586  	
587  	void
588  	crm_enable_blackbox(int nsig)
589  	{
590  	    if (qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_STATE_GET,
591  	                   0) == QB_LOG_STATE_ENABLED) {
592  	        return;
593  	    }
594  	
595  	    // Any size change drops existing entries
596  	    qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_SIZE, 5 * 1024 * 1024);
597  	
598  	    // Setting the size seems to disable the log target
599  	    qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_ENABLED, QB_TRUE);
600  	
601  	    /* Enable synchronous logging for each target except QB_LOG_SYSLOG and
602  	     * QB_LOG_STDERR
603  	     */
604  	    for (int i = QB_LOG_BLACKBOX; i < QB_LOG_TARGET_MAX; i++) {
605  	        qb_log_ctl(i, QB_LOG_CONF_FILE_SYNC, QB_TRUE);
606  	    }
607  	
608  	    pcmk__notice("Initiated blackbox recorder: %s", blackbox_file_prefix);
609  	
610  	    // Save to disk on abnormal termination
611  	    crm_signal_handler(SIGSEGV, enable_and_write_blackbox);
612  	    crm_signal_handler(SIGABRT, enable_and_write_blackbox);
613  	    crm_signal_handler(SIGILL, enable_and_write_blackbox);
614  	    crm_signal_handler(SIGBUS, enable_and_write_blackbox);
615  	    crm_signal_handler(SIGFPE, enable_and_write_blackbox);
616  	
617  	    crm_update_callsites();
618  	
619  	    blackbox_trigger = qb_log_custom_open(blackbox_logger, NULL, NULL, NULL);
620  	    qb_log_ctl(blackbox_trigger, QB_LOG_CONF_ENABLED, QB_TRUE);
621  	    pcmk__trace("Trigger: %d is %d %d", blackbox_trigger,
622  	                qb_log_ctl(blackbox_trigger, QB_LOG_CONF_STATE_GET, 0),
623  	                QB_LOG_STATE_ENABLED);
624  	
625  	    crm_update_callsites();
626  	}
627  	
628  	void
629  	crm_disable_blackbox(int nsig)
630  	{
631  	    if (qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_STATE_GET,
632  	                   0) != QB_LOG_STATE_ENABLED) {
633  	        return;
634  	    }
635  	
636  	    qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_ENABLED, QB_FALSE);
637  	
638  	    // Disable synchronous logging again when the blackbox is disabled
639  	    for (int i = QB_LOG_BLACKBOX; i < QB_LOG_TARGET_MAX; i++) {
640  	        qb_log_ctl(i, QB_LOG_CONF_FILE_SYNC, QB_FALSE);
641  	    }
642  	}
643  	
644  	/*!
645  	 * \internal
646  	 * \brief Write out a blackbox, if blackboxes are enabled
647  	 *
648  	 * \param[in] nsig  Signal that was received
649  	 * \param[in] cs    libqb callsite
650  	 *
651  	 * \note This may be called via a true signal handler and so must be async-safe.
652  	 * @TODO actually make this async-safe
653  	 */
654  	void
655  	crm_write_blackbox(int nsig, const struct qb_log_callsite *cs)
656  	{
657  	    static volatile int counter = 1;
658  	    static volatile time_t last = 0;
659  	
660  	    char *buffer = NULL;
661  	    int rc = 0;
662  	    time_t now = time(NULL);
663  	
664  	    if (blackbox_file_prefix == NULL) {
665  	        return;
666  	    }
667  	
668  	    switch (nsig) {
669  	        case 0:
670  	        case SIGTRAP:
671  	            /* The graceful case - such as assertion failure or user request */
672  	
673  	            if (nsig == 0 && now == last) {
674  	                /* Prevent over-dumping */
675  	                return;
676  	            }
677  	
678  	            buffer = pcmk__assert_asprintf("%s.%d", blackbox_file_prefix,
679  	                                           counter++);
680  	            if (nsig == SIGTRAP) {
681  	                pcmk__notice("Blackbox dump requested, please see %s for "
682  	                             "contents",
683  	                             buffer);
684  	
685  	            } else if (cs) {
686  	                syslog(LOG_NOTICE,
687  	                       "Problem detected at %s:%d (%s), please see %s for additional details",
688  	                       cs->function, cs->lineno, cs->filename, buffer);
689  	            } else {
690  	                pcmk__notice("Problem detected, please see %s for additional "
691  	                             "details",
692  	                             buffer);
693  	            }
694  	
695  	            last = now;
696  	
697  	            rc = qb_log_blackbox_write_to_file(buffer);
698  	            if (rc < 0) {
699  	                // System errno
700  	                pcmk__err("Failed to write blackbox file %s: %s", buffer,
701  	                          strerror(-rc));
702  	            }
703  	
704  	            /* Flush the existing contents
705  	             * A size change would also work
706  	             */
707  	            qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_ENABLED, QB_FALSE);
708  	            qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_ENABLED, QB_TRUE);
709  	            break;
710  	
711  	        default:
712  	            /* Do as little as possible, just try to get what we have out
713  	             * We logged the filename when the blackbox was enabled
714  	             */
715  	            crm_signal_handler(nsig, SIG_DFL);
716  	            qb_log_blackbox_write_to_file((const char *)blackbox_file_prefix);
717  	            qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_ENABLED, QB_FALSE);
718  	            raise(nsig);
719  	            break;
720  	    }
721  	
722  	    free(buffer);
723  	}
724  	
725  	static const char *
726  	crm_quark_to_string(uint32_t tag)
727  	{
728  	    const char *text = g_quark_to_string(tag);
729  	
730  	    if (text) {
731  	        return text;
732  	    }
733  	    return "";
734  	}
735  	
736  	static void
737  	crm_log_filter_source(int source, struct qb_log_callsite *cs)
738  	{
739  	    if (qb_log_ctl(source, QB_LOG_CONF_STATE_GET, 0) != QB_LOG_STATE_ENABLED) {
740  	        return;
741  	    }
742  	
743  	    if ((cs->tags != crm_trace_nonlog) && (source == QB_LOG_BLACKBOX)) {
744  	        /* Blackbox gets everything if enabled */
745  	        qb_bit_set(cs->targets, source);
746  	        return;
747  	    }
748  	
749  	    if ((source == blackbox_trigger) && (blackbox_trigger > 0)) {
750  	        /* Should this log message result in the blackbox being dumped */
751  	        if (cs->priority <= LOG_ERR) {
752  	            qb_bit_set(cs->targets, source);
753  	
754  	        } else if (trace_blackbox != NULL) {
755  	            char *key = pcmk__assert_asprintf("%s:%d", cs->function,
756  	                                              cs->lineno);
757  	
758  	            if (pcmk__g_strv_contains(trace_blackbox, key)) {
759  	                qb_bit_set(cs->targets, source);
760  	            }
761  	            free(key);
762  	        }
763  	        return;
764  	    }
765  	
766  	    if (source == QB_LOG_SYSLOG) {
767  	        // No tracing to syslog
768  	        if ((cs->priority <= crm_log_priority)
769  	            && (cs->priority <= crm_log_level)) {
770  	
771  	            qb_bit_set(cs->targets, source);
772  	        }
773  	        return;
774  	    }
775  	
776  	    if (cs->priority <= crm_log_level) {
777  	        qb_bit_set(cs->targets, source);
778  	        return;
779  	    }
780  	
781  	    if ((trace_files != NULL)
782  	        && pcmk__g_strv_contains(trace_files, cs->filename)) {
783  	
784  	        qb_bit_set(cs->targets, source);
785  	        return;
786  	    }
787  	
788  	    if ((trace_functions != NULL)
789  	        && pcmk__g_strv_contains(trace_functions, cs->function)) {
790  	
791  	        qb_bit_set(cs->targets, source);
792  	        return;
793  	    }
794  	
795  	    if ((trace_formats != NULL)
796  	        && pcmk__g_strv_contains(trace_formats, cs->format)) {
797  	
798  	        qb_bit_set(cs->targets, source);
799  	        return;
800  	    }
801  	
802  	    if ((cs->tags != 0) && (cs->tags != crm_trace_nonlog)
803  	        && (g_quark_to_string(cs->tags) != NULL)) {
804  	
805  	        qb_bit_set(cs->targets, source);
806  	    }
807  	}
808  	
809  	static void
810  	crm_log_filter(struct qb_log_callsite *cs)
811  	{
812  	    cs->targets = 0;            /* Reset then find targets to enable */
813  	    for (int i = QB_LOG_SYSLOG; i < QB_LOG_TARGET_MAX; i++) {
814  	        crm_log_filter_source(i, cs);
815  	    }
816  	}
817  	
818  	/*!
819  	 * \internal
820  	 * \brief Parse environment variables specifying which objects to trace
821  	 */
822  	static void
823  	init_tracing(void)
824  	{
825  	    const char *blackbox = pcmk__env_option(PCMK__ENV_TRACE_BLACKBOX);
826  	    const char *files = pcmk__env_option(PCMK__ENV_TRACE_FILES);
827  	    const char *formats = pcmk__env_option(PCMK__ENV_TRACE_FORMATS);
828  	    const char *functions = pcmk__env_option(PCMK__ENV_TRACE_FUNCTIONS);
829  	    const char *tags = pcmk__env_option(PCMK__ENV_TRACE_TAGS);
830  	
831  	    if (blackbox != NULL) {
832  	        trace_blackbox = g_strsplit(blackbox, ",", 0);
833  	    }
834  	
835  	    if (files != NULL) {
836  	        trace_files = g_strsplit(files, ",", 0);
837  	        tracing_enabled = true;
838  	    }
839  	
840  	    if (formats != NULL) {
841  	        trace_formats = g_strsplit(formats, ",", 0);
842  	        tracing_enabled = true;
843  	    }
844  	
845  	    if (functions != NULL) {
846  	        trace_functions = g_strsplit(functions, ",", 0);
847  	        tracing_enabled = true;
848  	    }
849  	
850  	    if (tags != NULL) {
851  	        gchar **trace_tags = g_strsplit(tags, ",", 0);
852  	
853  	        for (gchar **tag = trace_tags; *tag != NULL; tag++) {
854  	            if (pcmk__str_empty(*tag)) {
855  	                continue;
856  	            }
857  	
858  	            pcmk__info("Created GQuark %lld from token '%s' in '%s'",
859  	                       (long long) g_quark_from_string(*tag), *tag, tags);
860  	            tracing_enabled = true;
861  	        }
862  	
863  	        // We have the GQuarks, so we don't need the array anymore
864  	        g_strfreev(trace_tags);
865  	    }
866  	}
867  	
868  	/*!
869  	 * \internal
870  	 * \brief Free arrays of parsed trace objects
871  	 *
872  	 * \note GQuarks for trace tags can't be removed.
873  	 */
874  	static void
875  	cleanup_tracing(void)
876  	{
877  	    g_clear_pointer(&trace_blackbox, g_strfreev);
878  	    g_clear_pointer(&trace_files, g_strfreev);
879  	    g_clear_pointer(&trace_formats, g_strfreev);
880  	    g_clear_pointer(&trace_functions, g_strfreev);
881  	    tracing_enabled = false;
882  	}
883  	
884  	gboolean
885  	crm_is_callsite_active(struct qb_log_callsite *cs, uint8_t level, uint32_t tags)
886  	{
887  	    gboolean refilter = FALSE;
888  	
889  	    if (cs == NULL) {
890  	        return FALSE;
891  	    }
892  	
893  	    if (cs->priority != level) {
894  	        cs->priority = level;
895  	        refilter = TRUE;
896  	    }
897  	
898  	    if (cs->tags != tags) {
899  	        cs->tags = tags;
900  	        refilter = TRUE;
901  	    }
902  	
903  	    if (refilter) {
904  	        crm_log_filter(cs);
905  	    }
906  	
907  	    if (cs->targets == 0) {
908  	        return FALSE;
909  	    }
910  	    return TRUE;
911  	}
912  	
913  	void
914  	crm_update_callsites(void)
915  	{
916  	    static bool log = true;
917  	
918  	    if (log) {
919  	        log = false;
920  	        pcmk__debug("Enabling callsites based on priority=%d, files=%s, "
921  	                    "functions=%s, formats=%s, tags=%s",
922  	                    crm_log_level,
923  	                    pcmk__s(pcmk__env_option(PCMK__ENV_TRACE_FILES), "<null>"),
924  	                    pcmk__s(pcmk__env_option(PCMK__ENV_TRACE_FUNCTIONS),
925  	                            "<null>"),
926  	                    pcmk__s(pcmk__env_option(PCMK__ENV_TRACE_FORMATS),
927  	                            "<null>"),
928  	                    pcmk__s(pcmk__env_option(PCMK__ENV_TRACE_TAGS), "<null>"));
929  	    }
930  	    qb_log_filter_fn_set(crm_log_filter);
931  	}
932  	
933  	static int
934  	crm_priority2int(const char *name)
935  	{
936  	    struct syslog_names {
937  	        const char *name;
938  	        int priority;
939  	    };
940  	    static struct syslog_names p_names[] = {
941  	        {"emerg", LOG_EMERG},
942  	        {"alert", LOG_ALERT},
943  	        {"crit", LOG_CRIT},
944  	        {"error", LOG_ERR},
945  	        {"warning", LOG_WARNING},
946  	        {"notice", LOG_NOTICE},
947  	        {"info", LOG_INFO},
948  	        {"debug", LOG_DEBUG},
949  	        {NULL, -1}
950  	    };
951  	
952  	    for (int i = 0; (name != NULL) && (p_names[i].name != NULL); i++) {
953  	        if (pcmk__str_eq(p_names[i].name, name, pcmk__str_none)) {
954  	            return p_names[i].priority;
955  	        }
956  	    }
957  	    return crm_log_priority;
958  	}
959  	
960  	
961  	/*!
962  	 * \internal
963  	 * \brief Set the identifier for the current process
964  	 *
965  	 * If the identifier crm_system_name is not already set, then it is set as follows:
966  	 * - it is passed to the function via the "entity" parameter, or
967  	 * - it is derived from the executable name
968  	 *
969  	 * The identifier can be used in logs, IPC, and more.
970  	 *
971  	 * This method also sets the PCMK_service environment variable.
972  	 *
973  	 * \param[in] entity  If not NULL, will be assigned to the identifier
974  	 * \param[in] argc    The number of command line parameters
975  	 * \param[in] argv    The command line parameter values
976  	 */
977  	static void
978  	set_identity(const char *entity, int argc, char *const *argv)
979  	{
980  	    if (crm_system_name != NULL) {
981  	        return; // Already set, don't overwrite
982  	    }
983  	
984  	    if (entity != NULL) {
985  	        crm_system_name = pcmk__str_copy(entity);
986  	
987  	    } else if ((argc > 0) && (argv != NULL)) {
988  	        char *mutable = strdup(argv[0]);
989  	        char *modified = basename(mutable);
990  	
991  	        if (strstr(modified, "lt-") == modified) {
992  	            modified += 3;
993  	        }
994  	        crm_system_name = pcmk__str_copy(modified);
995  	        free(mutable);
996  	
997  	    } else {
998  	        crm_system_name = pcmk__str_copy("Unknown");
999  	    }
1000 	
1001 	    // Used by fencing.py.py (in fence-agents)
1002 	    pcmk__set_env_option(PCMK__ENV_SERVICE, crm_system_name, false);
1003 	}
1004 	
1005 	void
1006 	crm_log_preinit(const char *entity, int argc, char *const *argv)
1007 	{
1008 	    /* Configure libqb logging with nothing turned on */
1009 	    static bool have_logging = false;
1010 	    struct utsname res = { 0, };
1011 	    const pid_t pid = getpid();
1012 	    const char *nodename = "localhost";
1013 	
1014 	    if (have_logging) {
1015 	        return;
1016 	    }
1017 	
1018 	    have_logging = true;
1019 	
1020 	    init_tracing();
1021 	
1022 	    /* @TODO Try to create a more obvious "global Pacemaker initializer"
1023 	     * function than crm_log_preinit(), and call pcmk__schema_init() there.
1024 	     * See also https://projects.clusterlabs.org/T840.
1025 	     */
1026 	    pcmk__schema_init();
1027 	
1028 	    if (crm_trace_nonlog == 0) {
1029 	        crm_trace_nonlog = g_quark_from_static_string("Pacemaker non-logging tracepoint");
1030 	    }
1031 	
1032 	    umask(S_IWGRP | S_IWOTH | S_IROTH);
1033 	
1034 	    set_glib_log_handlers();
1035 	
1036 	    /* glib should not abort for any messages from the Pacemaker domain, but
1037 	     * other domains are still free to specify their own behavior.  However,
1038 	     * note that G_LOG_LEVEL_ERROR is always fatal regardless of what we do
1039 	     * here.
1040 	     */
1041 	    g_log_set_fatal_mask(G_LOG_DOMAIN, 0);
1042 	
1043 	    /* Set crm_system_name, which is used as the logging name. It may also
1044 	     * be used for other purposes such as an IPC client name.
1045 	     */
1046 	    set_identity(entity, argc, argv);
1047 	
1048 	    qb_log_init(crm_system_name, qb_log_facility2int("local0"), LOG_ERR);
1049 	    crm_log_level = LOG_CRIT;
1050 	
1051 	    /* Nuke any syslog activity until it's asked for */
1052 	    qb_log_ctl(QB_LOG_SYSLOG, QB_LOG_CONF_ENABLED, QB_FALSE);
1053 	#ifdef HAVE_qb_log_conf_QB_LOG_CONF_MAX_LINE_LEN
1054 	    // Shorter than default, generous for what we *should* send to syslog
1055 	    qb_log_ctl(QB_LOG_SYSLOG, QB_LOG_CONF_MAX_LINE_LEN, 256);
1056 	#endif
1057 	
1058 	    if ((uname(&res) == 0) && !pcmk__str_empty(res.nodename)) {
1059 	        nodename = res.nodename;
1060 	    }
1061 	
1062 	    /* Set format strings and disable threading
1063 	     * Pacemaker and threads do not mix well (due to the amount of forking)
1064 	     */
1065 	    qb_log_tags_stringify_fn_set(crm_quark_to_string);
1066 	    for (int i = QB_LOG_SYSLOG; i < QB_LOG_TARGET_MAX; i++) {
1067 	        qb_log_ctl(i, QB_LOG_CONF_THREADED, QB_FALSE);
1068 	#ifdef HAVE_qb_log_conf_QB_LOG_CONF_ELLIPSIS
1069 	        // End truncated lines with '...'
1070 	        qb_log_ctl(i, QB_LOG_CONF_ELLIPSIS, QB_TRUE);
1071 	#endif
1072 	        set_format_string(i, pid, nodename);
1073 	    }
1074 	
1075 	#ifdef ENABLE_NLS
1076 	    /* Enable translations (experimental). Currently we only have a few
1077 	     * proof-of-concept translations for some option help. The goal would be to
1078 	     * offer translations for option help and man pages rather than logs or
1079 	     * documentation, to reduce the burden of maintaining them.
1080 	     */
1081 	
1082 	    // Load locale information for the local host from the environment
1083 	    setlocale(LC_ALL, "");
1084 	
1085 	    // Tell gettext where to find Pacemaker message catalogs
1086 	    pcmk__assert(bindtextdomain(PACKAGE, PCMK__LOCALE_DIR) != NULL);
1087 	
1088 	    // Tell gettext to use the Pacemaker message catalogs
1089 	    pcmk__assert(textdomain(PACKAGE) != NULL);
1090 	
1091 	    // Tell gettext that the translated strings are stored in UTF-8
1092 	    bind_textdomain_codeset(PACKAGE, "UTF-8");
1093 	#endif
1094 	}
1095 	
1096 	gboolean
1097 	crm_log_init(const char *entity, uint8_t level, gboolean daemon, gboolean to_stderr,
1098 	             int argc, char **argv, gboolean quiet)
1099 	{
1100 	    const char *syslog_priority = NULL;
1101 	    const char *facility = pcmk__env_option(PCMK__ENV_LOGFACILITY);
1102 	    const char *f_copy = facility;
1103 	
1104 	    pcmk__is_daemon = daemon;
1105 	    crm_log_preinit(entity, argc, argv);
1106 	
1107 	    if (level > LOG_TRACE) {
1108 	        level = LOG_TRACE;
1109 	    }
1110 	    if(level > crm_log_level) {
1111 	        crm_log_level = level;
1112 	    }
1113 	
1114 	    /* Should we log to syslog */
1115 	    if (facility == NULL) {
1116 	        if (pcmk__is_daemon) {
1117 	            facility = "daemon";
1118 	        } else {
1119 	            facility = PCMK_VALUE_NONE;
1120 	        }
1121 	        pcmk__set_env_option(PCMK__ENV_LOGFACILITY, facility, true);
1122 	    }
1123 	
1124 	    if (pcmk__str_eq(facility, PCMK_VALUE_NONE, pcmk__str_casei)) {
1125 	        quiet = TRUE;
1126 	
1127 	
1128 	    } else {
1129 	        qb_log_ctl(QB_LOG_SYSLOG, QB_LOG_CONF_FACILITY, qb_log_facility2int(facility));
1130 	    }
1131 	
1132 	    if (pcmk__env_option_enabled(crm_system_name, PCMK__ENV_DEBUG)) {
1133 	        /* Override the default setting */
1134 	        crm_log_level = LOG_DEBUG;
1135 	    }
1136 	
1137 	    /* What lower threshold do we have for sending to syslog */
1138 	    syslog_priority = pcmk__env_option(PCMK__ENV_LOGPRIORITY);
1139 	    if (syslog_priority) {
1140 	        crm_log_priority = crm_priority2int(syslog_priority);
1141 	    }
1142 	    qb_log_filter_ctl(QB_LOG_SYSLOG, QB_LOG_FILTER_ADD, QB_LOG_FILTER_FILE, "*",
1143 	                      crm_log_priority);
1144 	
1145 	    // Log to syslog unless requested to be quiet
1146 	    if (!quiet) {
1147 	        qb_log_ctl(QB_LOG_SYSLOG, QB_LOG_CONF_ENABLED, QB_TRUE);
1148 	    }
1149 	
1150 	    /* Should we log to stderr */ 
1151 	    if (pcmk__env_option_enabled(crm_system_name, PCMK__ENV_STDERR)) {
1152 	        /* Override the default setting */
1153 	        to_stderr = TRUE;
1154 	    }
1155 	    crm_enable_stderr(to_stderr);
1156 	
1157 	    // Log to a file if we're a daemon or user asked for one
1158 	    {
1159 	        const char *logfile = pcmk__env_option(PCMK__ENV_LOGFILE);
1160 	
1161 	        if (pcmk__is_daemon || (logfile != NULL)) {
1162 	            // Daemons always get a log file, unless explicitly set to "none"
1163 	            pcmk__add_logfile(logfile);
1164 	        }
1165 	    }
1166 	
1167 	    if (pcmk__is_daemon
1168 	        && pcmk__env_option_enabled(crm_system_name, PCMK__ENV_BLACKBOX)) {
1169 	        crm_enable_blackbox(0);
1170 	    }
1171 	
1172 	    /* Summary */
1173 	    pcmk__trace("Quiet: %d, facility %s", quiet, f_copy);
1174 	    pcmk__env_option(PCMK__ENV_LOGFILE);
1175 	    pcmk__env_option(PCMK__ENV_LOGFACILITY);
1176 	
1177 	    crm_update_callsites();
1178 	
1179 	    /* Ok, now we can start logging... */
1180 	
1181 	    // Disable daemon request if user isn't root or Pacemaker daemon user
1182 	    if (pcmk__is_daemon) {
1183 	        const char *user = getenv("USER");
1184 	
1185 	        if (user != NULL && !pcmk__strcase_any_of(user, "root", CRM_DAEMON_USER, NULL)) {
1186 	            pcmk__trace("Not switching to corefile directory for %s", user);
1187 	            pcmk__is_daemon = false;
1188 	        }
1189 	    }
1190 	
1191 	    if (pcmk__is_daemon) {
1192 	        char *user = pcmk__uid2username(getuid());
1193 	
1194 	        if (user == NULL) {
1195 	            // Error already logged
1196 	
1197 	        } else if (!pcmk__str_any_of(user, "root", CRM_DAEMON_USER, NULL)) {
1198 	            pcmk__trace("Don't change active directory for regular user %s",
1199 	                        user);
1200 	
1201 	        } else if (chdir(CRM_CORE_DIR) < 0) {
1202 	            pcmk__info("Cannot change active directory to " CRM_CORE_DIR ": %s",
1203 	                       strerror(errno));
1204 	
1205 	        } else {
1206 	            pcmk__info("Changed active directory to " CRM_CORE_DIR);
1207 	        }
1208 	
1209 	        blackbox_file_prefix = pcmk__assert_asprintf(CRM_BLACKBOX_DIR
1210 	                                                     "/%s-%lld",
1211 	                                                     crm_system_name,
1212 	                                                     (long long) getpid());
1213 	        /* Original meanings from signal(7)
1214 	         *
1215 	         * Signal       Value     Action   Comment
1216 	         * SIGTRAP        5        Core    Trace/breakpoint trap
1217 	         * SIGUSR1     30,10,16    Term    User-defined signal 1
1218 	         * SIGUSR2     31,12,17    Term    User-defined signal 2
1219 	         *
1220 	         * Our usage is as similar as possible
1221 	         */
1222 	        mainloop_add_signal(SIGUSR1, crm_enable_blackbox);
1223 	        mainloop_add_signal(SIGUSR2, crm_disable_blackbox);
1224 	        mainloop_add_signal(SIGTRAP, enable_and_write_blackbox);
1225 	
1226 	        free(user);
1227 	
1228 	    } else if (!quiet) {
1229 	        crm_log_args(argc, argv);
1230 	    }
1231 	
1232 	    return TRUE;
1233 	}
1234 	
1235 	/*!
1236 	 * \internal
1237 	 * \brief Free the logging library's internal data structures
1238 	 */
1239 	void
1240 	crm_log_deinit(void)
1241 	{
1242 	    remove_glib_log_handlers();
1243 	    cleanup_tracing();
1244 	
(1) Event path: Condition "logger_out != NULL", taking true branch.
1245 	    if (logger_out != NULL) {
1246 	        logger_out->finish(logger_out, CRM_EX_OK, true, NULL);
(2) Event path: Condition "_p", taking true branch.
1247 	        g_clear_pointer(&logger_out, pcmk__output_free);
1248 	    }
1249 	
CID (unavailable; MK=283b458125dc38f982d8d8c365af93dd) (#2 of 3): 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".
1250 	    g_clear_pointer(&blackbox_file_prefix, free);
1251 	    g_clear_pointer(&crm_system_name, free);
1252 	}
1253 	
1254 	/* returns the old value */
1255 	unsigned int
1256 	set_crm_log_level(unsigned int level)
1257 	{
1258 	    unsigned int old = crm_log_level;
1259 	
1260 	    if (level > LOG_TRACE) {
1261 	        level = LOG_TRACE;
1262 	    }
1263 	    crm_log_level = level;
1264 	    crm_update_callsites();
1265 	    pcmk__trace("New log level: %d", level);
1266 	    return old;
1267 	}
1268 	
1269 	void
1270 	crm_enable_stderr(int enable)
1271 	{
1272 	    if (enable && qb_log_ctl(QB_LOG_STDERR, QB_LOG_CONF_STATE_GET, 0) != QB_LOG_STATE_ENABLED) {
1273 	        qb_log_ctl(QB_LOG_STDERR, QB_LOG_CONF_ENABLED, QB_TRUE);
1274 	        crm_update_callsites();
1275 	
1276 	    } else if (enable == FALSE) {
1277 	        qb_log_ctl(QB_LOG_STDERR, QB_LOG_CONF_ENABLED, QB_FALSE);
1278 	    }
1279 	}
1280 	
1281 	/*!
1282 	 * \brief Make logging more verbose
1283 	 *
1284 	 * If logging to stderr is not already enabled when this function is called,
1285 	 * enable it. Otherwise, increase the log level by 1.
1286 	 *
1287 	 * \param[in] argc  Ignored
1288 	 * \param[in] argv  Ignored
1289 	 */
1290 	void
1291 	crm_bump_log_level(int argc, char **argv)
1292 	{
1293 	    if (qb_log_ctl(QB_LOG_STDERR, QB_LOG_CONF_STATE_GET, 0)
1294 	        != QB_LOG_STATE_ENABLED) {
1295 	        crm_enable_stderr(TRUE);
1296 	    } else {
1297 	        set_crm_log_level(crm_log_level + 1);
1298 	    }
1299 	}
1300 	
1301 	unsigned int
1302 	get_crm_log_level(void)
1303 	{
1304 	    return crm_log_level;
1305 	}
1306 	
1307 	/*!
1308 	 * \brief Log the command line (once)
1309 	 *
1310 	 * \param[in]  Number of values in \p argv
1311 	 * \param[in]  Command-line arguments (including command name)
1312 	 *
1313 	 * \note This function will only log once, even if called with different
1314 	 *       arguments.
1315 	 */
1316 	void
1317 	crm_log_args(int argc, char **argv)
1318 	{
1319 	    static bool logged = false;
1320 	    gchar *arg_string = NULL;
1321 	
1322 	    if ((argc == 0) || (argv == NULL) || logged) {
1323 	        return;
1324 	    }
1325 	    logged = true;
1326 	    arg_string = g_strjoinv(" ", argv);
1327 	    pcmk__notice("Invoked: %s", arg_string);
1328 	    g_free(arg_string);
1329 	}
1330 	
1331 	void
1332 	crm_log_output_fn(const char *file, const char *function, int line, int level, const char *prefix,
1333 	                  const char *output)
1334 	{
1335 	    gchar **out_lines = NULL;
1336 	
1337 	    if (level == PCMK__LOG_NEVER) {
1338 	        return;
1339 	    }
1340 	
1341 	    if (output == NULL) {
1342 	        if (level != PCMK__LOG_STDOUT) {
1343 	            level = LOG_TRACE;
1344 	        }
1345 	        output = "-- empty --";
1346 	    }
1347 	
1348 	    out_lines = g_strsplit(output, "\n", 0);
1349 	
1350 	    for (gchar **out_line = out_lines; *out_line != NULL; out_line++) {
1351 	        do_crm_log_alias(level, file, function, line, "%s [ %s ]",
1352 	                         prefix, *out_line);
1353 	    }
1354 	
1355 	    g_strfreev(out_lines);
1356 	}
1357 	
1358 	void
1359 	pcmk__cli_init_logging(const char *name, unsigned int verbosity)
1360 	{
1361 	    crm_log_init(name, LOG_ERR, FALSE, FALSE, 0, NULL, TRUE);
1362 	
1363 	    for (int i = 0; i < verbosity; i++) {
1364 	        /* These arguments are ignored, so pass placeholders. */
1365 	        crm_bump_log_level(0, NULL);
1366 	    }
1367 	}
1368 	
1369 	/*!
1370 	 * \brief Log XML line-by-line in a formatted fashion
1371 	 *
1372 	 * \param[in] file      File name to use for log filtering
1373 	 * \param[in] function  Function name to use for log filtering
1374 	 * \param[in] line      Line number to use for log filtering
1375 	 * \param[in] tags      Logging tags to use for log filtering
1376 	 * \param[in] level     Priority at which to log the messages
1377 	 * \param[in] text      Prefix for each line
1378 	 * \param[in] xml       XML to log
1379 	 *
1380 	 * \note This does nothing when \p level is \c PCMK__LOG_STDOUT.
1381 	 * \note Do not call this function directly. It should be called only from the
1382 	 *       \p do_crm_log_xml() macro.
1383 	 */
1384 	void
1385 	pcmk_log_xml_as(const char *file, const char *function, uint32_t line,
1386 	                uint32_t tags, uint8_t level, const char *text, const xmlNode *xml)
1387 	{
1388 	    if (xml == NULL) {
1389 	        do_crm_log(level, "%s%sNo data to dump as XML",
1390 	                   pcmk__s(text, ""), pcmk__str_empty(text)? "" : " ");
1391 	
1392 	    } else {
1393 	        if (logger_out == NULL) {
1394 	            CRM_CHECK(pcmk__log_output_new(&logger_out) == pcmk_rc_ok, return);
1395 	        }
1396 	
1397 	        pcmk__output_set_log_level(logger_out, level);
1398 	        pcmk__output_set_log_filter(logger_out, file, function, line, tags);
1399 	        pcmk__xml_show(logger_out, text, xml, 1,
1400 	                       pcmk__xml_fmt_pretty
1401 	                       |pcmk__xml_fmt_open
1402 	                       |pcmk__xml_fmt_children
1403 	                       |pcmk__xml_fmt_close);
1404 	        pcmk__output_set_log_filter(logger_out, NULL, NULL, 0U, 0U);
1405 	    }
1406 	}
1407 	
1408 	/*!
1409 	 * \internal
1410 	 * \brief Log XML changes line-by-line in a formatted fashion
1411 	 *
1412 	 * \param[in] file      File name to use for log filtering
1413 	 * \param[in] function  Function name to use for log filtering
1414 	 * \param[in] line      Line number to use for log filtering
1415 	 * \param[in] tags      Logging tags to use for log filtering
1416 	 * \param[in] level     Priority at which to log the messages
1417 	 * \param[in] xml       XML whose changes to log
1418 	 *
1419 	 * \note This does nothing when \p level is \c PCMK__LOG_STDOUT.
1420 	 */
1421 	void
1422 	pcmk__log_xml_changes_as(const char *file, const char *function, uint32_t line,
1423 	                         uint32_t tags, uint8_t level, const xmlNode *xml)
1424 	{
1425 	    if (xml == NULL) {
1426 	        do_crm_log(level, "No XML to dump");
1427 	        return;
1428 	    }
1429 	
1430 	    if (logger_out == NULL) {
1431 	        CRM_CHECK(pcmk__log_output_new(&logger_out) == pcmk_rc_ok, return);
1432 	    }
1433 	    pcmk__output_set_log_level(logger_out, level);
1434 	    pcmk__output_set_log_filter(logger_out, file, function, line, tags);
1435 	    pcmk__xml_show_changes(logger_out, xml);
1436 	    pcmk__output_set_log_filter(logger_out, NULL, NULL, 0U, 0U);
1437 	}
1438 	
1439 	/*!
1440 	 * \internal
1441 	 * \brief Log an XML patchset line-by-line in a formatted fashion
1442 	 *
1443 	 * \param[in] file      File name to use for log filtering
1444 	 * \param[in] function  Function name to use for log filtering
1445 	 * \param[in] line      Line number to use for log filtering
1446 	 * \param[in] tags      Logging tags to use for log filtering
1447 	 * \param[in] level     Priority at which to log the messages
1448 	 * \param[in] patchset  XML patchset to log
1449 	 *
1450 	 * \note This does nothing when \p level is \c PCMK__LOG_STDOUT.
1451 	 */
1452 	void
1453 	pcmk__log_xml_patchset_as(const char *file, const char *function, uint32_t line,
1454 	                          uint32_t tags, uint8_t level, const xmlNode *patchset)
1455 	{
1456 	    if (patchset == NULL) {
1457 	        do_crm_log(level, "No patchset to dump");
1458 	        return;
1459 	    }
1460 	
1461 	    if (logger_out == NULL) {
1462 	        CRM_CHECK(pcmk__log_output_new(&logger_out) == pcmk_rc_ok, return);
1463 	    }
1464 	    pcmk__output_set_log_level(logger_out, level);
1465 	    pcmk__output_set_log_filter(logger_out, file, function, line, tags);
1466 	    logger_out->message(logger_out, "xml-patchset", patchset);
1467 	    pcmk__output_set_log_filter(logger_out, NULL, NULL, 0U, 0U);
1468 	}
1469 	
1470 	void pcmk__set_config_error_handler(pcmk__config_error_func error_handler, void *error_context)
1471 	{
1472 	    pcmk__config_error_handler = error_handler;
1473 	    pcmk__config_error_context = error_context;    
1474 	}
1475 	
1476 	void pcmk__set_config_warning_handler(pcmk__config_warning_func warning_handler, void *warning_context)
1477 	{
1478 	    pcmk__config_warning_handler = warning_handler;
1479 	    pcmk__config_warning_context = warning_context;   
1480 	}
1481