/* > logmessage.c
 *
 * Code for creating messages and sending them to stderr and/or to syslog.
 * Also has fatal_error() function to the same then exit.
 *
 * NOTE: We can't use malloc() here as one reason for a call could be the
 * out-of-memory condition, so we use a modest stack-based buffer for the
 * string "printing" before dumping it to the terminal and/or syslog.
 *
 * (c) 2013 Paul S. Crawford (psc@sat.dundee.ac.uk) licensed under GPL v2
 *
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "logmessage.h"

#define MAX_MESSAGE		1024
#define MAX_PROG_NAME	256

static int output_message(int level, char *buf);

static int using_syslog = 0;
static int using_terminal = 0;
static char progname[MAX_PROG_NAME];

/*
 * Prepare for message printing.
 *
 * On the 1st call to this should include the program's name (e.g. argv[])
 * but after that you can use NULL.
 *
 * The integer flags enable/disable output to either the terminal (via 'stderr')
 * or to syslog (assuming it is compiled as such, otherwise terminal as well).
 */

int open_logging(const char *name, int flags)
{
	int rv = 0;

	if (name != NULL)
		strncpy(progname, name, sizeof(progname) - 1);

	using_terminal = (flags & MSG_TO_STDERR);

	if (flags & MSG_TO_SYSLOG) {
		if (!using_syslog) {
#if USE_SYSLOG
			openlog(progname, LOG_PID, LOG_DAEMON);
#endif /*USE_SYSLOG */
			using_syslog = 1;	/* Future messages to syslog. */
		}
	} else {
		close_logging();
	}

	return rv;
}

/*
 * Output a message with a given priority level. Used internally for both
 * log_message() and fatal_error() calls. When using syslog we can output
 * twice, but without syslog either mode is directed to the terminal once.
 */

static int output_message(int level, char *buf)
{
	FILE *fp = stderr;
	int rv = 0;

#if USE_SYSLOG
	if (using_syslog) {
		syslog(level, "%s", buf);
	}

	if (using_terminal) {
#else
	if (using_terminal || using_syslog) {
#endif	/* !USE_SYSLOG */
		rv = fprintf(fp, "%s: %s\n", progname, buf);
		if(rv < 0 || fflush(fp)) {
		/* Error writing out to terminal - don't bother trying again. */
		using_terminal = 0;

#if USE_SYSLOG
		syslog(level, "failed writing message terminal (rv=%d, errno='%s')", rv, strerror(errno));
#endif /* USE_SYSLOG */
		}
	}

	return rv;
}

/*
 * Log a message to syslog and/or the terminal.
 *
 * NOTE: Unlike syslog you can't use '%m' formatting for error codes, instead use
 * the string '%s' and give it strerror(errno) as an argument.
 */

int log_message(int level, const char *fmt, ...)
{
	int rv = 0;
	char buf[MAX_MESSAGE];
	va_list args;

	memset(buf, 0, sizeof(buf));

	va_start(args, fmt);
#if _BSD_SOURCE || _XOPEN_SOURCE >= 500 || _ISOC99_SOURCE || _POSIX_C_SOURCE >= 200112L
	vsnprintf(buf, sizeof(buf) - 1, fmt, args);
#else
	vsprintf(buf, fmt, args);
#endif
	va_end(args);

	rv = output_message(level, buf);

	return rv;
}

/*
 * Function to log a message then exit program with a given error code.
 */

void fatal_error(int exitcode, const char *fmt, ...)
{
	char buf[MAX_MESSAGE];
	va_list args;

	memset(buf, 0, sizeof(buf));

	va_start(args, fmt);
#if _BSD_SOURCE || _XOPEN_SOURCE >= 500 || _ISOC99_SOURCE || _POSIX_C_SOURCE >= 200112L
	vsnprintf(buf, sizeof(buf) - 1, fmt, args);
#else
	vsprintf(buf, fmt, args);
#endif
	va_end(args);

	output_message(LOG_ERR, buf);
	close_logging();

#if defined(DEBUG)
	/*
	 * This should trigger a core dump when in debug mode, allowing trace-back
	 * to find out why we were leaving unexpectedly.
	 */
	assert(!buf);
#endif /*DEBUG*/
	exit(exitcode);
}

/*
 * Stop any logging via syslog.
 *
 * Return is 0 is stopped, or -1 if not in use.
 *
 * Note you don't need to use this, it is also possible to call something
 * like open_logging(NULL, MSG_TO_STDERR) to close syslog.
 */

int close_logging(void)
{
	int rv = -1;

	/* Log the closing message */
	if (using_syslog) {
#if USE_SYSLOG
		closelog();
#endif /* USE_SYSLOG */
		using_syslog = 0;
		rv = 0;
	}

	return rv;
}
