/*
 * Copyright 2003-2015, Axel Dörfler, axeld@pinc-software.de.
 * Distributed under the terms of the MIT License.
 */
 
 
#include <syslog.h>
 
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
 
#include <launch.h>
#include <syslog_daemon.h>
#include <TLS.h>
#include <util/KMessage.h>
 
 
struct syslog_context {
	char	ident[B_OS_NAME_LENGTH];
	int16	mask;
	int16	facility;
	int32	options;
};
 
static syslog_context sTeamContext = {
	"",
	-1,
	LOG_USER,
	LOG_CONS
};
static int32 sThreadContextSlot = -1;
static port_id sSystemLoggerPort = -1;
 
 
static syslog_context *
allocate_context()
{
	syslog_context *context = (syslog_context *)malloc(sizeof(syslog_context));
	if (context == NULL)
		return NULL;
 
	// inherit the attributes of the team context
	memcpy(context, &sTeamContext, sizeof(syslog_context));
	return context;
}
 
 
/*! This function returns the current syslog context structure.
	If there is none for the current thread, it will create one
	that inherits the context attributes from the team and put it
	into TLS.
	If it could not allocate a thread context, it will return the
	team context; this function is guaranteed to return a valid
	syslog context.
*/
static syslog_context *
get_context()
{
	if (sThreadContextSlot == B_NO_MEMORY)
		return &sTeamContext;
 
	if (sThreadContextSlot < 0) {
		static int32 lock = 0;
		if (atomic_add(&lock, 1) == 0) {
			int32 slot = tls_allocate();
 
			if (slot < 0) {
				sThreadContextSlot = B_NO_MEMORY;
				return &sTeamContext;
			}
 
			sThreadContextSlot = slot;
		} else {
			while (sThreadContextSlot == -1)
				snooze(10000);
		}
	}
 
	syslog_context *context = (syslog_context *)tls_get(sThreadContextSlot);
	if (context == NULL) {
		// try to allocate the context again; it might have
		// been deleted, or there was not enough memory last
		// time
		*tls_address(sThreadContextSlot) = context = allocate_context();
	}
	if (context != NULL)
		return context;
 
	return &sTeamContext;
}
 
 
//! Prints simplified syslog message to stderr.
static void
message_to_console(syslog_context *context, const char *text, va_list args)
{
	if (context->ident[0])
		fprintf(stderr, "'%s' ", context->ident);
	if (context->options & LOG_PID)
		fprintf(stderr, "[%" B_PRId32 "] ", find_thread(NULL));
 
	vfprintf(stderr, text, args);
	fputc('\n', stderr);
}
 
 
/*!	Retrieves the port of the system logger from the launch_daemon.
*/
static port_id
get_system_logger_port()
{
	if (sSystemLoggerPort >= 0)
		return sSystemLoggerPort;
 
	BPrivate::KMessage data;
	if (BPrivate::get_launch_data(B_SYSTEM_LOGGER_SIGNATURE, data) == B_OK)
		sSystemLoggerPort = data.GetInt32("logger_port", -1);
 
	return sSystemLoggerPort;
}
 
 
/*!	Creates the message from the given context and sends it to the syslog
	daemon, if the priority mask matches.
	If the message couldn't be delivered, and LOG_CONS was set, it will
	redirect the message to stderr.
*/
static void
send_syslog_message(syslog_context *context, int priority, const char *text,
	va_list args)
{
	int options = context->options;
 
	// do we have to do anything?
	if ((context->mask & LOG_MASK(SYSLOG_PRIORITY(priority))) == 0)
		return;
 
	port_id port = get_system_logger_port();
	if ((options & LOG_PERROR) != 0
		|| ((options & LOG_CONS) != 0 && port < B_OK)) {
		// if asked for, print out the (simplified) message on stderr
		message_to_console(context, text, args);
	}
	if (port < B_OK) {
		// apparently, there is no syslog daemon running;
		return;
	}
 
	// adopt facility from openlog() if not yet set
	if (SYSLOG_FACILITY(priority) == 0)
		priority |= context->facility;
 
	char buffer[2048];
	syslog_message &message = *(syslog_message *)&buffer[0];
 
	message.from = find_thread(NULL);
	message.when = real_time_clock();
	message.options = options;
	message.priority = priority;
	strcpy(message.ident, context->ident);
 
	int length = vsnprintf(message.message, sizeof(buffer)
		- sizeof(syslog_message), text, args);
	if (message.message + length - buffer < (int32)sizeof(buffer)) {
		if (length == 0 || message.message[length - 1] != '\n')
			message.message[length++] = '\n';
	} else
		buffer[length - 1] = '\n';
 
	status_t status;
	do {
		// make sure the message gets send (if there is a valid port)
		status = write_port(port, SYSLOG_MESSAGE, &message,
			sizeof(syslog_message) + length);
	} while (status == B_INTERRUPTED);
 
	if (status < B_OK && (options & LOG_CONS) != 0
		&& (options & LOG_PERROR) == 0) {
		// LOG_CONS redirects all output to the console in case contacting
		// the syslog daemon failed
		message_to_console(context, text, args);
	}
}
 
 
//	#pragma mark - POSIX API
 
 
void
closelog(void)
{
	closelog_thread();
}
 
 
void
openlog(const char *ident, int options, int facility)
{
	openlog_thread(ident, options, facility);
}
 
 
int
setlogmask(int priorityMask)
{
	return setlogmask_thread(priorityMask);
}
 
 
void
syslog(int priority, const char *message, ...)
{
	va_list args;
 
	va_start(args, message);
	send_syslog_message(get_context(), priority, message, args);
	va_end(args);
}
 
 
//	#pragma mark - Be extensions
// ToDo: it would probably be better to export these symbols as weak symbols only
 
 
void
closelog_team(void)
{
	// nothing to do here...
}
 
 
void
openlog_team(const char *ident, int options, int facility)
{
	if (ident != NULL)
		strlcpy(sTeamContext.ident, ident, sizeof(sTeamContext.ident));
 
	sTeamContext.options = options;
	sTeamContext.facility = SYSLOG_FACILITY(facility);
}
 
 
int
setlogmask_team(int priorityMask)
{
	int oldMask = sTeamContext.mask;
 
	if (priorityMask != 0)
		sTeamContext.mask = priorityMask;
 
	return oldMask;
}
 
 
void
log_team(int priority, const char *message, ...)
{
	va_list args;
 
	va_start(args, message);
	send_syslog_message(&sTeamContext, priority, message, args);
	va_end(args);
}
 
 
void
closelog_thread(void)
{
	if (sThreadContextSlot < 0)
		return;
 
	free(tls_get(sThreadContextSlot));
	*tls_address(sThreadContextSlot) = NULL;
}
 
 
void
openlog_thread(const char *ident, int options, int facility)
{
	syslog_context *context = get_context();
 
	if (ident)
		strcpy(context->ident, ident);
 
	context->options = options;
	context->facility = SYSLOG_FACILITY(facility);
}
 
 
int
setlogmask_thread(int priorityMask)
{
	syslog_context *context = get_context();
	int oldMask = context->mask;
 
	if (priorityMask != 0)
		context->mask = priorityMask;
 
	return oldMask;
}
 
 
void
log_thread(int priority, const char *message, ...)
{
	va_list args;
 
	va_start(args, message);
	send_syslog_message(get_context(), priority, message, args);
	va_end(args);
}
 
 
//	#pragma mark - BSD extensions
 
 
void
vsyslog(int priority, const char *message, va_list args)
{
	send_syslog_message(get_context(), priority, message, args);
}
 

V512 A call of the 'vsnprintf' function will lead to overflow of the buffer 'message.message'.