/* -*- mode: c; c-file-style: "openbsd" -*- */
/*	$OpenBSD: log.c,v 1.11 2007/12/07 17:17:00 reyk Exp $	*/

/*
 * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
 * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <syslog.h>
#include <sys/types.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include "log.h"

/* By default, logging is done on stderr. */
static int use_syslog = 0;
/* Default debug level */
static int debug = 0;

/* Logging can be modified by providing an appropriate log handler. */
static void (*logh)(int severity, const char *msg) = NULL;

static void vlog(int, const char *, const char *, va_list);
static void logit(int, const char *, const char *, ...);

#define MAX_DBG_TOKENS 40
static const char *tokens[MAX_DBG_TOKENS + 1] = { NULL };

void
log_init(int n_syslog, int n_debug, const char *progname)
{
	use_syslog = n_syslog;
	debug = n_debug;

	if (use_syslog) openlog(progname, LOG_PID | LOG_NDELAY, LOG_DAEMON);

	tzset();
}

void
log_level(int n_debug)
{
	if (n_debug >= 0) debug = n_debug;
}

void
log_register(void (*cb)(int, const char *))
{
	logh = cb;
}

void
log_accept(const char *token)
{
	int i;
	for (i = 0; i < MAX_DBG_TOKENS; i++) {
		if (tokens[i] == NULL) {
			tokens[i + 1] = NULL;
			tokens[i] = token;
			return;
		}
	}
}

static void
logit(int pri, const char *token, const char *fmt, ...)
{
	va_list ap;

	va_start(ap, fmt);
	vlog(pri, token, fmt, ap);
	va_end(ap);
}

static char *
date()
{
	/* Return the current date as incomplete ISO 8601 (2012-12-12T16:13:30) */
	static char date[] = "2012-12-12T16:13:30";
	time_t t = time(NULL);
	struct tm *tmp = localtime(&t);
	strftime(date, sizeof(date), "%Y-%m-%dT%H:%M:%S", tmp);
	return date;
}

static const char *
translate(int fd, int priority)
{
	/* Translate a syslog priority to a string. With colors if the output is a
	 * terminal. */
	int tty = isatty(fd);
	switch (tty) {
	case 1:
		switch (priority) {
		case LOG_EMERG:
			return "\033[1;37;41m[EMRG";
		case LOG_ALERT:
			return "\033[1;37;41m[ALRT";
		case LOG_CRIT:
			return "\033[1;37;41m[CRIT";
		case LOG_ERR:
			return "\033[1;31m[ ERR";
		case LOG_WARNING:
			return "\033[1;33m[WARN";
		case LOG_NOTICE:
			return "\033[1;34m[NOTI";
		case LOG_INFO:
			return "\033[1;34m[INFO";
		case LOG_DEBUG:
			return "\033[36m[ DBG";
		}
		break;
	default:
		switch (priority) {
		case LOG_EMERG:
			return "[EMRG";
		case LOG_ALERT:
			return "[ALRT";
		case LOG_CRIT:
			return "[CRIT";
		case LOG_ERR:
			return "[ ERR";
		case LOG_WARNING:
			return "[WARN";
		case LOG_NOTICE:
			return "[NOTI";
		case LOG_INFO:
			return "[INFO";
		case LOG_DEBUG:
			return "[ DBG";
		}
	}
	return "[UNKN]";
}

static void
vlog(int pri, const char *token, const char *fmt, va_list ap)
{
	if (logh) {
		char *result = NULL;
		if (vasprintf(&result, fmt, ap) != -1) {
			logh(pri, result);
			free(result);
			return;
		}
		/* Otherwise, abort. We don't know if "ap" is still OK. We could
		 * have made a copy, but this is too much overhead for a
		 * situation that shouldn't happen. */
		return;
	}

	/* Log to syslog if requested */
	if (use_syslog) {
		va_list ap2;
		va_copy(ap2, ap);
		vsyslog(pri, fmt, ap2);
		va_end(ap2);
	}

	/* Log to standard error in all cases */
	char *nfmt;
	/* best effort in out of mem situations */
	if (asprintf(&nfmt, "%s %s%s%s]%s %s\n", date(), translate(STDERR_FILENO, pri),
		token ? "/" : "", token ? token : "",
		isatty(STDERR_FILENO) ? "\033[0m" : "", fmt) == -1) {
		vfprintf(stderr, fmt, ap);
		fprintf(stderr, "\n");
	} else {
		vfprintf(stderr, nfmt, ap);
		free(nfmt);
	}
	fflush(stderr);
}

void
log_warn(const char *token, const char *emsg, ...)
{
	char *nfmt = NULL;
	va_list ap;

	/* best effort to even work in out of memory situations */
	if (emsg == NULL)
		logit(LOG_WARNING, "%s", strerror(errno));
	else {
		va_start(ap, emsg);

		if (asprintf(&nfmt, "%s: %s", emsg, strerror(errno)) == -1) {
			/* we tried it... */
			vlog(LOG_WARNING, token, emsg, ap);
			logit(LOG_WARNING, "%s", strerror(errno));
		} else {
			vlog(LOG_WARNING, token, nfmt, ap);
			free(nfmt);
		}
		va_end(ap);
	}
}

void
log_warnx(const char *token, const char *emsg, ...)
{
	va_list ap;

	va_start(ap, emsg);
	vlog(LOG_WARNING, token, emsg, ap);
	va_end(ap);
}

void
log_info(const char *token, const char *emsg, ...)
{
	va_list ap;

	if (use_syslog || debug > 0 || logh) {
		va_start(ap, emsg);
		vlog(LOG_INFO, token, emsg, ap);
		va_end(ap);
	}
}

static int
log_debug_accept_token(const char *token)
{
	int i;
	if (tokens[0] == NULL) return 1;
	for (i = 0; (i < MAX_DBG_TOKENS) && (tokens[i] != NULL); i++) {
		if (!strcmp(tokens[i], token)) return 1;
	}
	return 0;
}

void
log_debug(const char *token, const char *emsg, ...)
{
	va_list ap;

	if ((debug > 1 && log_debug_accept_token(token)) || logh) {
		va_start(ap, emsg);
		vlog(LOG_DEBUG, token, emsg, ap);
		va_end(ap);
	}
}

void
fatal(const char *token, const char *emsg)
{
	if (emsg == NULL)
		logit(LOG_CRIT, token ? token : "fatal", "%s", strerror(errno));
	else if (errno)
		logit(LOG_CRIT, token ? token : "fatal", "%s: %s", emsg,
		    strerror(errno));
	else
		logit(LOG_CRIT, token ? token : "fatal", "%s", emsg);

	exit(1);
}

void
fatalx(const char *token, const char *emsg)
{
	errno = 0;
	fatal(token, emsg);
}
