/* $OpenLDAP: pkg/ldap/libraries/liblutil/debug.c,v 1.26.2.1 2002/05/22 14:25:46 kurt Exp $ */
/*
 * Copyright 1998-2002 The OpenLDAP Foundation, All Rights Reserved.
 * COPYING RESTRICTIONS APPLY, see COPYRIGHT file
 */

#include "portable.h"

#include <stdio.h>

#include <ac/stdarg.h>
#include <ac/stdlib.h>
#include <ac/string.h>
#include <ac/time.h>

#ifdef LDAP_SYSLOG
#include <ac/syslog.h>
#endif

#include "ldap_log.h"
#include "ldap_defaults.h"
#include "lber.h"

struct DEBUGLEVEL
{
	char *subsystem;
	int  level;
};

static struct DEBUGLEVEL **levelArray;
static long   numLevels = 0;

static FILE *log_file = NULL;
static int global_level = 0;

#ifdef LDAP_SYSLOG
static int use_syslog = 0;

static int debug2syslog(int l) {
	switch (l) {
	case LDAP_LEVEL_EMERG:	return LOG_EMERG;
	case LDAP_LEVEL_ALERT:	return LOG_ALERT;
	case LDAP_LEVEL_CRIT:	return LOG_CRIT;
	case LDAP_LEVEL_ERR:	return LOG_ERR;
	case LDAP_LEVEL_WARNING:	return LOG_WARNING;
	case LDAP_LEVEL_NOTICE:	return LOG_NOTICE;
	case LDAP_LEVEL_INFO:	return LOG_INFO;
	}
	return LOG_DEBUG;
}
#endif

static char *lutil_levels[] = {"emergency", "alert", "critical",
			   "error", "warning", "notice",
			   "information", "entry", "args",
			   "results", "detail1", "detail2",
			   NULL};

int lutil_mnem2level( const char *level )
{
    int i;
    for( i = 0; lutil_levels[i] != NULL; i++ )
    {
	if ( !strcasecmp( level, lutil_levels[i] ) )
	{
	    return i;
	}
    }
    return 0;
}

static void addSubsys( const char *subsys, int level )
{
	int i, j;

	if ( !strcasecmp( subsys, "global") ) global_level = level;

	for( i = 0; i < numLevels; i++ )
	{
		if ( levelArray[i] == NULL )
		{
			levelArray[i] = (struct DEBUGLEVEL*)ber_memalloc( sizeof( struct DEBUGLEVEL ) );
			levelArray[i]->subsystem = (char*)ber_memalloc( strlen( subsys ) + 1 );
			strcpy ( levelArray[i]->subsystem, subsys );
			levelArray[i]->level = level;
			return;
		}
		if( !strcasecmp( subsys, levelArray[i]->subsystem ) )
		{
			levelArray[i]->level = level;
			return;
		}
	}
	levelArray = (struct DEBUGLEVEL**)ber_memrealloc( levelArray, sizeof( struct DEBUGLEVEL* ) * (numLevels + 10) );
	for( j = numLevels; j < (numLevels + 10); j++ )
	{
		levelArray[j] = NULL;
	}
	numLevels += 10;
	levelArray[i] = (struct DEBUGLEVEL*)ber_memalloc( sizeof( struct DEBUGLEVEL ) );
	levelArray[i]->subsystem = (char*)ber_memalloc( strlen( subsys ) + 1 );
	strcpy( levelArray[i]->subsystem, subsys );
	levelArray[i]->level = level;
	return;
}

void lutil_set_debug_level( const char* subsys, int level )
{
    addSubsys( subsys, level );
}

int lutil_debug_file( FILE *file )
{
	log_file = file;
	ber_set_option( NULL, LBER_OPT_LOG_PRINT_FILE, file );

	return 0;
}

void lutil_log_int(
	FILE* file,
	const char *subsys, int level,
	const char *fmt, va_list vl )
{
#ifdef HAVE_WINSOCK
	time_t now;
	struct tm *today;
#endif
	int i;

	if ( levelArray == NULL ) return; /* logging isn't set up */

	/*
	 * Look for the subsystem in the level array.  When we find it,
	 * break out of the loop.
	 */
	for( i = 0; i < numLevels; i++ ) {
		if ( levelArray[i] == NULL ) break; 
		if ( ! strcasecmp( levelArray[i]->subsystem, subsys ) ) break;
	}

	/*
	 * If we didn't find the subsystem, or the set level is less than
	 * the requested output level, don't output it.
	 */
	if ( (level > global_level) &&
		((i > numLevels ) || (levelArray[i] == NULL) || ( level > levelArray[i]->level )) )
	{
		return;
	}

#ifdef LDAP_SYSLOG
	/* we're configured to use syslog */
	if( use_syslog ) {
		vsyslog( debug2syslog(level), fmt, vl );
		return;
	}
#endif

#if 0
#ifdef HAVE_WINSOCK
	if( log_file == NULL ) {
		log_file = fopen( LDAP_RUNDIR LDAP_DIRSEP "openldap.log", "w" );

		if ( log_file == NULL )
			log_file = fopen( "openldap.log", "w" );

		if ( log_file == NULL )
			return;

		ber_set_option( NULL, LBER_OPT_LOG_PRINT_FILE, log_file );
	}
#endif
#endif

	if( file == NULL ) {
		/*
		 * Use stderr unless file was specified via:
		 *   ber_set_option( NULL, LBER_OPT_LOG_PRINT_FILE, file)
		 */
		file = stderr;
	}

#ifdef HAVE_WINSOCK
	/*
	 * Stick the time in the buffer to output when using Winsock
	 * as NT can't pipe to a timestamp program like Unix can.
	 * This, of course, makes some logs hard to read.
     */
	time( &now );
	today = localtime( &now );
	fprintf( file, "%4d%02d%02d:%02d:%02d:%02d ",
		today->tm_year + 1900, today->tm_mon + 1,
		today->tm_mday, today->tm_hour,
		today->tm_min, today->tm_sec );
#endif

	/*
	 * format the output data.
	 */
	vfprintf( file, fmt, vl );
}

/*
 * The primary logging routine.	 Takes the subsystem being logged from, the
 * level of the log output and the format and data.  Send this on to the
 * internal routine with the print file, if any.
 */
void lutil_log( const char *subsys, int level, const char *fmt, ... )
{
	FILE* outfile = NULL;
	va_list vl;
	va_start( vl, fmt );
	ber_get_option( NULL, LBER_OPT_LOG_PRINT_FILE, &outfile );
	lutil_log_int( outfile, subsys, level, fmt, vl );
	va_end( vl );
}

void lutil_log_initialize(int argc, char **argv)
{
    int i;
    /*
     * Start by setting the hook for the libraries to use this logging
     * routine.
     */
    ber_set_option( NULL, LBER_OPT_LOG_PROC, (void*)lutil_log_int );

    if ( argc == 0 ) return;
    /*
     * Now go through the command line options to set the debugging
     * levels
     */
    for( i = 0; i < argc; i++ )
    {
	char *next = argv[i];
	if ( i < argc-1 && next[0] == '-' && next[1] == 'd' )
	{
	    char subsys[64];
	    int level;
	    char *optarg = argv[i+1];
	    char *index = strchr( optarg, '=' );
	    if ( index != NULL )
	    {
		*index = 0;
		strcpy ( subsys, optarg );
		level = atoi( index+1 );
		if ( level <= 0 ) level = lutil_mnem2level( index + 1 );
		lutil_set_debug_level( subsys, level );
		*index = '=';
	    }
	    else
	    {
		global_level = atoi( optarg );
		/* 
		 * if a negative number was used, make the global level the
		 * maximum sane level.
		 */
		if ( global_level < 0 ) global_level = 65535;
	    }
	}
    }
}

void (lutil_debug)( int debug, int level, const char *fmt, ... )
{
	char buffer[4096];
	va_list vl;

	if ( !(level & debug ) )
		return;

#ifdef HAVE_WINSOCK
	if( log_file == NULL ) {
		log_file = fopen( LDAP_RUNDIR LDAP_DIRSEP "openldap.log", "w" );

		if ( log_file == NULL )
			log_file = fopen( "openldap.log", "w" );

		if ( log_file == NULL )
			return;

		ber_set_option( NULL, LBER_OPT_LOG_PRINT_FILE, log_file );
	}
#endif
	va_start( vl, fmt );

#ifdef HAVE_VSNPRINTF
	vsnprintf( buffer, sizeof(buffer), fmt, vl );
#else
	vsprintf( buffer, fmt, vl );
#endif
	buffer[sizeof(buffer)-1] = '\0';

	if( log_file != NULL ) {
		fputs( buffer, log_file );
		fflush( log_file );
	}

    fputs( buffer, stderr );
	va_end( vl );
}
