/* -*- c-basic-offset: 4; tab-width: 8; indent-tabs-mode: t -*- */

/* Copyright (c) 2001-2007 International Computer Science Institute
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software")
 * to deal in the Software without restriction, subject to the conditions
 * listed in the XORP LICENSE file. These conditions include: you must
 * preserve this copyright notice, and you cannot mention the copyright
 * holders in advertising related to the Software without their permission.
 * The Software is provided WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED. This
 * notice is a summary of the XORP LICENSE file; the license in that file is
 * legally binding.
 */

#ident "$XORP: xorp/libxorp/xlog.c,v 1.22 2007/02/16 22:46:29 pavlin Exp $"

/*
 * Message logging utility.
 */

#include "libxorp_module.h"

#include "libxorp/xorp.h"

#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif

#include "xlog.h"


/*
 * Exported variables
 */

/*
 * Local constants definitions
 */
#define MAX_XLOG_OUTPUTS	10

/*
 * Local structures, typedefs and macros
 */
#ifdef UNUSED
#undef UNUSED
#endif
#define UNUSED(x) (x) = (x)

/*
 * Local variables
 */
static int		init_flag = 0;
static int		start_flag = 0;
static pid_t		pid = 0;
static char		*preamble_string = NULL;
static char		*process_name_string = NULL;
static FILE		*xlog_outputs_file[MAX_XLOG_OUTPUTS];
static xlog_output_func_t xlog_outputs_func[MAX_XLOG_OUTPUTS];
static void		*xlog_outputs_obj[MAX_XLOG_OUTPUTS];
static size_t		xlog_output_file_count = 0;
static size_t		xlog_output_func_count = 0;
static FILE		*fp_default = NULL;
static int		xlog_level_enabled[XLOG_LEVEL_MAX];
static xlog_verbose_t	xlog_verbose_level[XLOG_LEVEL_MAX];
/*
 * XXX: the log level names below has to be consistent with the XLOG_LEVEL_*
 * values.
 */
static const char	*xlog_level_names[XLOG_LEVEL_MAX] = {
				"FATAL",
				"ERROR",
				"WARNING",
				"INFO",
				"TRACE"
			};


/* State and functions for systems without varargs preprocessors */
static const char	*the_module = NULL;
static const char	*the_file = NULL;
static int		the_line = 0;
static const char	*the_func = NULL;

/*
 * Local functions prototypes
 */

static int	xlog_init_lock(void);
static int	xlog_init_unlock(void);
static int	xlog_exit_lock(void);
static int	xlog_exit_unlock(void);
static int	xlog_write_lock(void);
static int	xlog_write_unlock(void);
static void	xlog_record_va(xlog_level_t log_level, const char* module_name,
			       const char *where, const char* format,
			       va_list ap);
static int	xlog_write(FILE* fp, const char* fmt, ...);
static int	xlog_write_va(FILE* fp, const char* fmt, va_list ap);
static int	xlog_flush(FILE* fp);
static const char* xlog_localtime2string_short(void);


/*
 * ****************************************************************************
 * Control functions to init/exit/start/stop/etc the log utility.
 * ****************************************************************************
 */

/**
 * xlog_init:
 * @argv0 The path to the executable.
 * @preamble_message A string that will become part of the preamble string.
 *
 * Initialize the log utility.
 * As part of the initialization, the preamble string will be set to
 * <@module_name><@preamble_message>
 *
 * Return value: 0 on success, otherwise -1.
 **/
int
xlog_init(const char *argv0, const char *preamble_message)
{
    const char* process_name;
    xlog_level_t level;

    if (init_flag)
	return (-1);

    /* Get the init lock */
    if (xlog_init_lock() < 0) {
	fprintf(stderr, "Error obtaining xlog_init_lock()\n");
	exit (1);
    }

    pid = getpid();

    if (process_name_string != NULL) {
	free(process_name_string);
	process_name_string = NULL;
    }

    process_name = strrchr(argv0, '/');
    if (process_name != NULL)
	process_name++;		/* Skip the last '/' */
    if (process_name == NULL)
	process_name = argv0;

    if (process_name != NULL)
	process_name_string = strdup(process_name);

    /* Set the preamble string */
    xlog_set_preamble(preamble_message);

    /* Enable all log messages by default, and set default verbose level */
    for (level = XLOG_LEVEL_MIN; level < XLOG_LEVEL_MAX; level++) {
	xlog_enable(level);
	xlog_verbose_level[level] = XLOG_VERBOSE_LOW;		/* Default */
    }
    xlog_verbose_level[XLOG_LEVEL_FATAL] = XLOG_VERBOSE_HIGH;	/* XXX */

    init_flag = 1;

    /* Release the init lock */
    xlog_init_unlock();

    return (0);
}

/**
 * xlog_exit:
 * @void:
 *
 * Gracefully exit logging.
 *
 * Return value: 0 on success, othewise -1.
 **/
int
xlog_exit(void)
{
    int i;
    xlog_level_t level;

    if (! init_flag)
	return (-1);

    if (start_flag)
	xlog_stop();

    /* Get the exit lock */
    if (xlog_exit_lock() < 0) {
	return (-1);
    }

    /* Reset local variables */
    init_flag = 0;
    pid = 0;
    if (process_name_string != NULL) {
	free (process_name_string);
	process_name_string = NULL;
    }
    if (preamble_string != NULL) {
	free (preamble_string);
	preamble_string = NULL;
    }

    for (i = 0; i < MAX_XLOG_OUTPUTS; i++) {
	xlog_outputs_file[i] = NULL;
	xlog_outputs_func[i] = NULL;
	xlog_outputs_obj[i] = NULL;
    }
    xlog_output_file_count = 0;
    xlog_output_func_count = 0;
    fp_default = 0;

    for (level = XLOG_LEVEL_MIN; level < XLOG_LEVEL_MAX; level++) {
	xlog_disable(level);
	xlog_verbose_level[level] = XLOG_VERBOSE_LOW;		/* Default */
    }
    xlog_verbose_level[XLOG_LEVEL_FATAL] = XLOG_VERBOSE_HIGH;	/* XXX */

    /* Release the exit lock */
    xlog_exit_unlock();

    return (0);
}

/**
 * xlog_start:
 * @void:
 *
 * Start logging.
 *
 * Return value: 0 on success, otherwise -1.
 **/
int
xlog_start(void)
{
    if (! init_flag)
	return (-1);

    if (start_flag)
	return (-1);

    start_flag = 1;

    return (0);
}

/**
 * xlog_stop:
 * @void:
 *
 * Stop logging.
 *
 * Return value: 0 on success, otherwise -1.
 **/
int
xlog_stop(void)
{
    if (! start_flag)
	return (-1);

    start_flag = 0;

    return (0);
}

int
xlog_is_running(void)
{
    return start_flag;
}

/**
 * xlog_enable:
 * @log_level: The message type (e.g., %XLOG_LEVEL_WARNING) to enable
 * the logging for.
 *
 * Enable logging for messages of type &log_level_t.
 * By default, all message types are enabled.
 *
 * Return value: 0 on success, otherwise -1.
 **/
int
xlog_enable(xlog_level_t log_level)
{
    if (XLOG_LEVEL_MAX <= log_level)
	return (-1);

    xlog_level_enabled[log_level] = 1;

    return (0);
}

/**
 * xlog_disable:
 * @log_level: The message type (e.g., %XLOG_LEVEL_WARNING) to disable
 * the logging for.
 *
 * Enable logging for messages of type &log_level_t.
 * XXX: %XLOG_LEVEL_FATAL cannot be disabled.
 *
 * Return value: 0 on success, otherwise -1.
 **/
int
xlog_disable(xlog_level_t log_level)
{
    if (XLOG_LEVEL_MAX <= log_level)
	return (-1);

    /* XXX: XLOG_LEVEL_FATAL can never be disabled */
    if (log_level == XLOG_LEVEL_FATAL)
	return (-1);

    xlog_level_enabled[log_level] = 0;

    return (0);
}

/**
 * xlog_set_preamble:
 * @text: The preamble string, or NULL if no preamble.
 *
 * Set the preamble string for the log entries.
 **/
void
xlog_set_preamble(const char* text)
{
    /* Free the memory for the old preamble */
    if (preamble_string != NULL) {
	free(preamble_string);
	preamble_string = NULL;
    }
    /* Duplicate the new preamble string */
    if (text != NULL) {
	preamble_string = strdup(text);
    }
}

/**
 * xlog_process_name:
 *
 * Get the process name as set by xlog_init.
 * Return value: pointer to process name on success, NULL otherwise.
 **/
const char*
xlog_process_name()
{
    return process_name_string;
}

/**
 * xlog_set_verbose:
 * @verbose_level: The level of verbosity #xlog_verbose_t
 * (higher is more verbose).
 *
 * Set the level of verbosity (#xlog_verbose_t) for the log entries.
 * Applies for all type of messages except for %XLOG_LEVEL_FATAL
 * which always is set to the most verbose level.
 **/
void
xlog_set_verbose(xlog_verbose_t verbose_level)
{
    int i;

    if (XLOG_VERBOSE_HIGH < verbose_level)
	verbose_level = XLOG_VERBOSE_HIGH;

    for (i = 0; i < XLOG_LEVEL_MAX; i++) {
	if (i == XLOG_LEVEL_FATAL)
	    continue;		/* XXX: XLOG_LEVEL_FATAL cannot be changed */
	xlog_verbose_level[i] = verbose_level;
    }
}

/**
 * xlog_level_set_verbose:
 * @log_level: The message type #xlog_level_t to set the verbosity of.
 * @verbose_level: The level of verbosity #xlog_verbose_t
 * (higher is more verbose).
 *
 * Set the level of verbosity (#xlog_verbose_t) for the log entries
 * of messages of type #xlog_level_t.
 * Note: %XLOG_LEVEL_FATAL verbosity cannot be changed, and is
 * always set to the most verbose level.
 **/
void
xlog_level_set_verbose(xlog_level_t log_level, xlog_verbose_t verbose_level)
{
    if (XLOG_LEVEL_MAX <= log_level)
	return;

    if (log_level == XLOG_LEVEL_FATAL)
	return;			/* XXX: XLOG_LEVEL_FATAL cannot be changed */

    if (XLOG_VERBOSE_HIGH < verbose_level)
	verbose_level = XLOG_VERBOSE_HIGH;

    xlog_verbose_level[log_level] = verbose_level;
}

/*
 * Macro function to generate xlog_info, xlog_warning, etc...
 */
#define xlog_fn(fn, log_level)					\
void									\
xlog_##fn (const char *module_name, const char *where, const char *fmt, ...) \
{									\
    va_list ap;								\
    va_start(ap, fmt);							\
    xlog_record_va(log_level, module_name, where, fmt, ap);		\
    va_end(ap);								\
}

/*
 * Macro function to generate a log function that aborts. E.g.: xlog_fatal
 * XXX: if signal SIGABRT is caught and the signal handler does not return,
 * the program will NOT terminate.
 */
#define xlog_fn_abort(fn, log_level)					\
void									\
xlog_##fn (const char *module_name, const char *where, const char *fmt, ...) \
{									\
    va_list ap;								\
    va_start(ap, fmt);							\
    xlog_record_va(log_level, module_name, where, fmt, ap);		\
    va_end(ap);								\
    abort();								\
}

/*
 * Generate the log functions
 */
xlog_fn_abort(fatal, 	XLOG_LEVEL_FATAL)
xlog_fn(error, 		XLOG_LEVEL_ERROR)
xlog_fn(warning,	XLOG_LEVEL_WARNING)
xlog_fn(info, 		XLOG_LEVEL_INFO)


/*
 * ****************************************************************************
 * Lock/unlock functions
 *
 * TODO: not implemented yet. Add whatever locks may be needed.
 * ****************************************************************************
 */

/**
 * xlog_init_lock:
 * @void:
 *
 * Obtain a lock needed during initialization of the log utility.
 *
 * Return value: 0 on success, otherwise -1.
 **/
static int
xlog_init_lock(void)
{
    return (0);
}

/**
 * xlog_init_unlock:
 * @void:
 *
 * Release the lock used during initialization of the log utility.
 *
 * Return value: 0 on success, otherwise -1.
 **/
static int
xlog_init_unlock(void)
{
    return (0);
}

/**
 * xlog_exit_lock:
 * @void:
 *
 * Obtain a lock needed during exit from the log utility.
 *
 * Return value: 0 on success, otherwise -1.
 **/
static int
xlog_exit_lock(void)
{
    return (0);
}

/**
 * xlog_exit_unlock:
 * @void:
 *
 * Release the lock used during exit from the log utility.
 *
 * Return value:
 **/
static int
xlog_exit_unlock(void)
{
    return (0);
}

/**
 * xlog_write_lock:
 * @void:
 *
 * Obtain a lock needed during writing of a log message.
 *
 * Return value: 0 on success, otherwise -1.
 **/
static int
xlog_write_lock(void)
{
    return (0);
}

/**
 * xlog_write_unlock:
 * @void:
 *
 * Release the lock used during writing of a log message.
 *
 * Return value: 0 on success, otherwise -1.
 **/
static int
xlog_write_unlock(void)
{
    return (0);
}

/*
 * ****************************************************************************
 * Print functions
 * ****************************************************************************
 */

/**
 * xlog_record_va:
 * @log_level: The log level of the message to write.
 * @module_name: The name of the module this message applies to.
 * @where: Where the log was generated (e.g., the file name, line number, etc).
 * @format: The printf() format of the message to write.
 * Note that a trailing newline is added if none is present.
 * @ap: The vararg list of arguments.
 *
 * Write a log message.
 **/
static void
xlog_record_va(xlog_level_t log_level, const char *module_name,
	       const char *where, const char *format, va_list ap)
{
    const char	*preamble_lead;
    const char	*process_name_lead;
    char	*buf_payload_ptr = NULL, *buf_preamble_ptr = NULL;
    char	*buf_output_ptr = NULL;
    int		buf_output_size;
    size_t	i;
#ifdef SIGPIPE
    sig_t	sigpipe_handler;
#endif

    if (! start_flag) {
	if (! init_flag)
	    fprintf(stderr,
		    "Logging must be initialized first by xlog_init()\n");
	if (! start_flag)
	    fprintf(stderr,
		    "Logging must be started first by xlog_start()\n");

	abort();
    }

    if ((xlog_output_file_count == 0) && (xlog_output_func_count == 0))
	return;

    if (XLOG_LEVEL_MAX <= log_level)
	return;			/* Invalid log level */

    if (! xlog_level_enabled[log_level])
	return;			/* The log level is disabled */

    xlog_write_lock();
#ifdef SIGPIPE
    sigpipe_handler = signal(SIGPIPE, SIG_IGN);
#endif

    preamble_lead = (preamble_string) ? preamble_string : "";
    process_name_lead = (process_name_string) ? process_name_string : "";

    /*
     * Prepare the preamble string to write.
     * XXX: we need to prepare it once, otherwise the time may be
     * different when we write to more than one outputs.
     */
    switch (xlog_verbose_level[log_level]) {
    case XLOG_VERBOSE_LOW:	/* The minimum log information */
	x_asprintf(&buf_preamble_ptr, "[ %s %s %s %s ] ",
		   xlog_localtime2string_short(),
		   xlog_level_names[log_level],
		   process_name_lead,
		   module_name);
	break;

    case XLOG_VERBOSE_MEDIUM:	/* Add preamble string if non-NULL */
	x_asprintf(&buf_preamble_ptr, "[ %s %s %s %s %s ] ",
		   xlog_localtime2string_short(),
		   preamble_lead,
		   xlog_level_names[log_level],
		   process_name_lead,
		   module_name);
	break;

    case XLOG_VERBOSE_HIGH:	/* Most verbose */
    default:
	x_asprintf(&buf_preamble_ptr, "[ %s %s %s %s:%d %s %s ] ",
		   xlog_localtime2string_short(),
		   preamble_lead,
		   xlog_level_names[log_level],
		   process_name_lead,
		   (int)pid,
		   module_name,
		   where);
	break;
    }

    /*
     * Prepare the payload string to write
     */
    x_vasprintf(&buf_payload_ptr, format, ap);

    if ((buf_preamble_ptr == NULL)
	&& ((buf_payload_ptr == NULL) || buf_payload_ptr[0] == '\0'))
	goto cleanup_label;

    /*
     * Prepare the output string to write.
     * XXX: here we explicitly add the  '\n' at the end of the
     * output string, because the XLOG message format implies it.
     */
    buf_output_size = x_asprintf(&buf_output_ptr, "%s%s\n",
				 buf_preamble_ptr, buf_payload_ptr);
    if ((buf_output_ptr == NULL)
	|| (buf_output_ptr[0] == '\0')
	|| (buf_output_size < 0)) {
	goto cleanup_label;
    }

    /* Remove our '\n' from the end if the payload itself already has one */
    if (buf_output_size >= 2) {
	char n1, n2;
	n1 = buf_output_ptr[buf_output_size - 2];
	n2 = buf_output_ptr[buf_output_size - 1];
	if ((n1 == '\n') && (n2 == '\n'))
	    buf_output_ptr[buf_output_size - 1] = '\0';
    }

    /*
     * Write to the file descriptors
     */
    for (i = 0; i < xlog_output_file_count; ) {
	FILE *fp = xlog_outputs_file[i];
	if (xlog_write(fp, "%s", buf_output_ptr) || xlog_flush(fp)) {
	    xlog_remove_output(fp);
	    continue;
	}
	i++;
    }

    /*
     * Write to the functions
     */
    for (i = 0; i < xlog_output_func_count; ) {
	xlog_output_func_t func = xlog_outputs_func[i];
	void *obj = xlog_outputs_obj[i];
	if (func(obj, buf_output_ptr) < 0) {
	    xlog_remove_output_func(func, obj);
	    continue;
	}
	i++;
    }

 cleanup_label:
    /*
     * Cleanup
     */
    if (buf_preamble_ptr)
	free(buf_preamble_ptr);
    if (buf_payload_ptr)
	free(buf_payload_ptr);
    if (buf_output_ptr)
	free(buf_output_ptr);
#ifdef SIGPIPE
    signal(SIGPIPE, sigpipe_handler);
#endif
    xlog_write_unlock();
}

/**
 * xlog_write:
 * @fp: The file descriptor to write to.
 * @fmt: The printf() style format of the message to write.
 * @:
 *
 * Format a message for writing and call a function to write it to @fp.
 *
 * Return value: 0 on success, otherwise -1.
 **/
static int
xlog_write(FILE* fp, const char* fmt, ...)
{
    va_list ap;
    int retval;

    va_start(ap, fmt);
    retval = xlog_write_va(fp, fmt, ap);
    va_end(ap);

    return (retval);
}

/**
 * xlog_write_va:
 * @fp: The file descriptor to write to.
 * @fmt: The printf() style format of the message to write.
 * @ap: The vararg list of arguments.
 *
 * Write a message to @fp.
 *
 * Return value: 0 on success, otherwise -1.
 **/
static int
xlog_write_va(FILE* fp, const char* fmt, va_list ap)
{
    assert(fp != NULL);
    if (vfprintf(fp, fmt, ap) < 0) {
	return (-1);
    }

    return (0);
}

/**
 * xlog_flush:
 * @fp: The file descriptor to flush.
 *
 * Flushes all buffered data for output stream @fp.
 *
 * Return value: 0 on success, otherwise -1.
 **/
static int
xlog_flush(FILE* fp)
{
    if (! fflush(fp))
	return (0);
    else
	return (-1);
}

/**
 * _xcond_trace_msg_long:
 * @module_name: The name of the module this message applies to.
 * @file: The file name where XLOG_TRACE() has occured.
 * @line: The line number where XLOG_TRACE() has occured.
 * @func: The function name where XLOG_TRACE() has occured.
 * @flag: The boolean flag that defines whether the messages should be printed.
 * @fmt: The printf() style format.
 * @:
 *
 * The entry function for XLOG_TRACE(). Used if the compiler supports
 * variable arguments for macros.
 * This function should not be called directly.
 **/
void
_xcond_trace_msg_long(const char	*module_name,
		      const char	*file,
		      int		line,
		      const char	*func,
		      int		flag,
		      const char	*fmt, ...)
{
    if (flag) {
	va_list ap;
	char xlog_where_buf[8000];

	/* TODO: use _xcond_trace_entry and _xcond_trace_msg_short instead */
	sprintf(xlog_where_buf, "+%d %s %s", line, file,
		(func) ? func : "(unknown_func)");
	va_start(ap, fmt);
	xlog_record_va(XLOG_LEVEL_TRACE, module_name, xlog_where_buf, fmt, ap);
	va_end(ap);
    }
}

/**
 * _xcond_trace_entry:
 * @module_name: The name of the module this message applies to.
 * @file: The file name where XLOG_TRACE() has occured.
 * @line: The line number where XLOG_TRACE() has occured.
 * @func: The function name where XLOG_TRACE() has occured.
 *
 * A helper function that sets the file name, line number and function name.
 * Needed if the compiler does not support variable arguments for macros.
 * This function should not be called directly.
 **/
void
_xcond_trace_entry(const char *module_name, const char *file,
		   int line, const char* func)
{
    the_module = module_name;
    the_file = file;
    the_line = line;
    the_func = func;
}

/**
 * _xcond_trace_msg_short:
 * @flag: The boolean flag that defines whether the messages should be printed.
 * @fmt: The printf() style format.
 * @:
 *
 * The entry function for XLOG_TRACE(). Used if the compiler does not support
 * variable arguments for macros.
 * This function should not be called directly.
 **/
void
_xcond_trace_msg_short(int flag, const char* fmt, ...)
{
    if (flag) {
	va_list ap;
	char xlog_where_buf[8000];

	sprintf(xlog_where_buf, "+%d %s %s", the_line, the_file,
		(the_func) ? the_func : "(unknown_func)");
	va_start(ap, fmt);
	xlog_record_va(XLOG_LEVEL_TRACE, the_module, xlog_where_buf, fmt, ap);
	va_end(ap);
    }
}

/*
 * ****************************************************************************
 * Output stream functions
 * ****************************************************************************
 */

/**
 * xlog_add_output:
 * @fp: The file descriptor to add to the set of output streams.
 *
 * Add a file descriptor to the set of output streams.
 *
 * Return value: 0 on success, otherwise -1.
 **/
int
xlog_add_output(FILE* fp)
{
    size_t i;

    for (i = 0; i < xlog_output_file_count; i++) {
	if (xlog_outputs_file[i] == fp) {
	    return (0);
	}
    }
    if (i < MAX_XLOG_OUTPUTS) {
	xlog_outputs_file[i] = fp;
	xlog_output_file_count++;
	return (0);
    }

    return (-1);
}

/**
 * xlog_remove_output:
 * @fp: The file descriptor to remove from the set of output streams.
 *
 * Remove a file descriptor from the set of output streams.
 *
 * Return value: 0 on success, otherwise -1.
 **/
int
xlog_remove_output(FILE* fp)
{
    size_t i, j;

    for (i = 0; i < xlog_output_file_count; i++) {
	if (xlog_outputs_file[i] == fp) {
	    for(j = i + 1; j < xlog_output_file_count; j++) {
		xlog_outputs_file[j - 1] = xlog_outputs_file[j];
	    }
	    xlog_output_file_count--;
	    return (0);
	}
    }

    return (-1);
}

/**
 * xlog_add_output_func:
 * @func: The function to add to the set of output streams.
 * @obj: The object to supply @func with when called.
 *
 * Add a processing function and an object to the set of output streams.
 *
 * Return value: 0 on success, otherwise -1.
 **/
int
xlog_add_output_func(xlog_output_func_t func, void *obj)
{
    size_t i;

    for (i = 0; i < xlog_output_func_count; i++) {
	if ((xlog_outputs_func[i] == func)
	    && (xlog_outputs_obj[i] == obj)) {
	    return (0);
	}
    }
    if (i < MAX_XLOG_OUTPUTS) {
	xlog_outputs_func[i] = func;
	xlog_outputs_obj[i] = obj;
	xlog_output_func_count++;
	return (0);
    }

    return (-1);
}

/**
 * xlog_remove_output_func:
 * @func: The function to remove from the set of output streams.
 * @obj: The object that @func was supplied with.
 *
 * Remove a processing function and an object from the set of output streams.
 *
 * Return value: 0 on success, otherwise -1.
 **/
int
xlog_remove_output_func(xlog_output_func_t func, void *obj)
{
    size_t i, j;

    for (i = 0; i < xlog_output_func_count; i++) {
	if ((xlog_outputs_func[i] == func)
	    && (xlog_outputs_obj[i] == obj)) {
	    for(j = i + 1; j < xlog_output_func_count; j++) {
		xlog_outputs_func[j - 1] = xlog_outputs_func[j];
		xlog_outputs_obj[j - 1] = xlog_outputs_obj[j];
	    }
	    xlog_output_func_count--;
	    return (0);
	}
    }

    return (-1);
}

/**
 * xlog_add_default_output:
 * @void:
 *
 * Add the default output stream to the set of output streams.
 * XXX: right now the default is '/dev/stderr', but it should eventually be:
 * `/dev/console' if the process has sufficient permissions,
 * and `/dev/stderr' otherwise.
 *
 * Return value: 0 on success, otherwise -1.
 **/
int
xlog_add_default_output(void)
{
#ifdef HOST_OS_WINDOWS
    FILE *fp = NULL;
    HANDLE hstd = INVALID_HANDLE_VALUE;

    /*
     * XXX: We may need to duplicate these handles;
     * fclose() on _open_osfhandle() derived handles results
     * in the underlying handle being closed.
     */
    hstd = GetStdHandle(STD_ERROR_HANDLE);
    fp = _fdopen(_open_osfhandle((long)hstd, _O_TEXT), "w");
    if (fp != NULL)
	return (xlog_add_output(fp));

    fp = fopen("CONOUT$", "w");
    if (fp != NULL)
	return (xlog_add_output(fp));

    hstd = GetStdHandle(STD_OUTPUT_HANDLE),
    fp = _fdopen(_open_osfhandle((long)hstd, _O_TEXT), "w");
    if (fp != NULL)
	return (xlog_add_output(fp));

#else /* !HOST_OS_WINDOWS */

    const char* defaults[] = {	/* The default outputs (in preference order) */
	"/dev/stderr",		/* XXX: temporary this is the default */
	"/dev/console",
	"/dev/stdout"
    };
    size_t ndefaults = sizeof(defaults) / sizeof(defaults[0]);
    size_t i;

    /*
     * Attempt to open default output stream, console first in case
     * we are root, then stderr.
     */
    for (i = 0; fp_default == NULL && i < ndefaults; i++) {
	if ((fp_default = fopen(defaults[i], "w")) != NULL) {
	    return (xlog_add_output(fp_default));
	}
    }
#endif /* HOST_OS_WINDOWS */

    return -1;
}

/**
 * xlog_remove_default_output:
 * @void:
 *
 * Remove the default output stream from the set of output streams.
 *
 * Return value: 0 on success, otherwise -1.
 **/
int
xlog_remove_default_output(void)
{
    int r = 0;

    if (fp_default != NULL) {
	r = xlog_remove_output(fp_default);
	fclose (fp_default);
	fp_default = NULL;
    }

    return (r);
}

/**
 * xlog_localtime2string_short:
 * @void:
 *
 * Compute the current local time and return it as a string in the format:
 *   Year/Month/Day Hour:Minute:Second
 *   Example: 2002/02/05 20:22:09
 *   Note that the returned string uses statically allocated memory,
 *   and does not need to be de-allocated.
 *
 * Return value: A statically allocated string with the local time using
 * the format described above.
 **/
static const char*
xlog_localtime2string_short(void)
{
    static char	  buf[64];
    static size_t buf_bytes = sizeof(buf) / sizeof(buf[0]);
    time_t	now = time(NULL);
    struct tm	*timeptr = localtime(&now);

    strftime(buf, buf_bytes, "%Y/%m/%d %H:%M:%S", timeptr);

    return (buf);
}

/**
 * xlog_localtime2string:
 * @void:
 *
 * Compute the current local time and return it as a string in the format:
 *   Year/Month/Day Hour:Minute:Second.Microsecond
 *   Example: 2002/02/05 20:22:09.808632
 *   Note that the returned string uses statically allocated memory,
 *   and does not need to be de-allocated.
 *
 * Return value: A statically allocated string with the local time using
 * the format described above.
 * XXX: This function uses a BSD-specific function, gettimeofday().
 * XXX: This function calls sprintf() without bounds checking.
 **/
const char *
xlog_localtime2string(void)
{
#ifdef HOST_OS_WINDOWS
    static char buf[sizeof("999999999/99/99 99/99/99.999999999 ")];
    SYSTEMTIME st;
    DWORD usecs;

    GetSystemTime(&st);
    usecs = st.wMilliseconds * 1000;
    snprintf(buf, sizeof(buf), "%u/%u/%u %u:%u:%u.%lu",
	     st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond,
	     usecs);
    return (buf);
#else /* !HOST_OS_WINDOWS */
    /* XXX: The code below may stop working after year 999999999 */
    char buf[sizeof("999999999/99/99 99/99/99.999999999 ")];
    static char ret_buf[sizeof(buf)];
    struct timeval tv;
    time_t clock;
    struct tm *tm;

    gettimeofday(&tv, NULL);
    clock = tv.tv_sec;
    tm = localtime(&clock);
    if (strftime(buf, sizeof(buf), "%Y/%m/%d %H:%M:%S", tm) == 0) {
	sprintf(ret_buf, "strftime ERROR");
	return (ret_buf);
    }

    sprintf(ret_buf, "%s.%lu", buf, (unsigned long)tv.tv_usec);

    return (ret_buf);
#endif /* HOST_OS_WINDOWS */
}

/**
 * x_vasprintf:
 * @ret: A pointer to the string pointer to store the result.
 * @format: The printf(3)-style format.
 * @ap: The variable arguments for @format.
 *
 * A local implementation of vasprintf(3).
 * Note that vasprintf(3) is not POSIX, so we don't attempt to
 * check whether it is available and use the system implementation
 * instead of our own below.
 * Part of the reasoning for not using it is technical:
 * on Linux vasprintf(3) is declared #ifdef __USE_GNU in <stdio.h>,
 * and it is not healthy to define __USE_GNU just to make the code
 * compile on Linux.
 *
 * Return value: (From FreeBSD vasprintf(3) manual page).
 * The number of characters printed (not including the
 * trailing '\0' used to end output to strings). Also, set @*ret to be
 * a pointer to a buffer sufficiently large to hold the formatted string.
 * This pointer should be passed to free(3) to release the allocated
 * storage when it is no longer needed. If sufficient space cannot
 * be allocated, will return -1 and set @ret to be a NULL pointer.
 *
 * TODO: this function and x_asprintf() below probably should
 * be renamed to vasprintf() and asprintf() and go somewhere else,
 * something like "compat.c" .
 **/
int
x_vasprintf(char **ret, const char *format, va_list ap)
{
    size_t i, buf_size = 1024 + 1;
    char *buf_ptr = NULL;
    int ret_size;
#ifdef HAVE_VA_COPY
    va_list temp;
#endif

    for (i = 0; i < 3; i++) {
	/*
	 * XXX: two iterations should be sufficient to compute the
	 * buffer size and allocate it, but anyway we try one more time
	 */
	buf_ptr = malloc(buf_size);
	if (buf_ptr == NULL)
	    break;		/* Cannot allocate memory */
	buf_ptr[0] = '\0';
	/* XXX
	 * va_copy does not exist in older compilers like gcc
	 * 2.95. On some architectures like the i386 using a va_list
	 * argument multiple times doesn't seem to cause a problem. On
	 * the AMD 64 it is required.
	 */
#ifdef HAVE_VA_COPY
 	va_copy(temp, ap);
	ret_size = vsnprintf(buf_ptr, buf_size, format, temp);
#else
	ret_size = vsnprintf(buf_ptr, buf_size, format, ap);
#endif
	if (ret_size < 0)
	    break;		/* Cannot format the string */
	if ((size_t)ret_size < buf_size) {
	    *ret = buf_ptr;
	    return (ret_size);
	}
	/* The allocated buffer was too small. Try again. */
	free(buf_ptr);
	buf_ptr = NULL;
	buf_size = ret_size + 1; /* XXX: include space for trailing '\0' */
    }

    /*
     * Error: cannot format the string or cannot allocate enough memory
     */
    if (buf_ptr != NULL)
	free(buf_ptr);
    *ret = NULL;

    return (-1);
}


/**
 * x_asprintf:
 * @ret: A pointer to the string pointer to store the result.
 * @format: The printf(3)-style format.
 * @...: The variable arguments for @format.
 *
 * A local implementation of asprintf(3) if it is missing.
 * If vasprintf(3) is available, it is called instead.
 *
 * Return value: (From FreeBSD asprintf(3) manual page).
 * The number of characters printed (not including the
 * trailing '\0' used to end output to strings). Also, set @*ret to be
 * a pointer to a buffer sufficiently large to hold the formatted string.
 * This pointer should be passed to free(3) to release the allocated
 * storage when it is no longer needed. If sufficient space cannot
 * be allocated, will return -1 and set @ret to be a NULL pointer.
 **/
int
x_asprintf(char **ret, const char *format, ...)
{
    va_list ap;
    int ret_size;

    va_start(ap, format);
    ret_size = x_vasprintf(ret, format, ap);
    va_end(ap);

    return (ret_size);
}



syntax highlighted by Code2HTML, v. 0.9.1