/*++
/* NAME
/*	msg 3
/* SUMMARY
/*	diagnostic interface
/* SYNOPSIS
/*	#include <msg.h>
/*
/*	int	msg_verbose;
/*
/*	void	msg_info(format, ...)
/*	const char *format;
/*
/*	void	vmsg_info(format, ap)
/*	const char *format;
/*	va_list	ap;
/*
/*	void	msg_warn(format, ...)
/*	const char *format;
/*
/*	void	vmsg_warn(format, ap)
/*	const char *format;
/*	va_list	ap;
/*
/*	void	msg_error(format, ...)
/*	const char *format;
/*
/*	void	vmsg_error(format, ap)
/*	const char *format;
/*	va_list	ap;
/*
/*	NORETURN msg_fatal(format, ...)
/*	const char *format;
/*
/*	NORETURN vmsg_fatal(format, ap)
/*	const char *format;
/*	va_list	ap;
/*
/*	NORETURN msg_fatal_status(status, format, ...)
/*	int	status;
/*	const char *format;
/*
/*	NORETURN vmsg_fatal_status(status, format, ap)
/*	int	status;
/*	const char *format;
/*	va_list	ap;
/*
/*	NORETURN msg_panic(format, ...)
/*	const char *format;
/*
/*	NORETURN vmsg_panic(format, ap)
/*	const char *format;
/*	va_list	ap;
/*
/*	MSG_CLEANUP_FN msg_cleanup(cleanup)
/*	void (*cleanup)(void);
/* AUXILIARY FUNCTIONS
/*	int	msg_error_limit(count)
/*	int	count;
/*
/*	void	msg_error_clear()
/*
/*	void	msg_set_longjmp_action(
/*	NORETURN (*longjmp_action) (int)
/* DESCRIPTION
/*	This module reports diagnostics. By default, diagnostics are sent
/*	to the standard error stream, but the disposition can be changed
/*	by the user. See the hints below in the SEE ALSO section.
/*
/*	msg_info(), msg_warn(), msg_error(), msg_fatal*() and msg_panic()
/*	produce a one-line record with the program name, a severity code
/*	(except for msg_info()), and an informative message. The program
/*	name must have been set by calling one of the msg_XXX_init()
/*	functions (see the SEE ALSO section).
/*
/*	msg_error() reports a recoverable error and increments the error
/*	counter. When the error count exceeds a pre-set limit (default: 13)
/*	the program terminates by calling msg_fatal().
/*
/*	msg_fatal() reports an unrecoverable error and terminates the program
/*	with a non-zero exit status.
/*
/*	msg_fatal_status() reports an unrecoverable error and terminates the
/*	program with the specified exit status.
/*
/*	msg_panic() reports an internal inconsistency, terminates the
/*	program immediately (i.e. without calling the optional user-specified
/*	cleanup routine), and forces a core dump when possible.
/*
/*	msg_cleanup() specifies a function that msg_fatal[_status]() should
/*	invoke before terminating the program, and returns the
/*	current function pointer. Specify a null argument to disable
/*	this feature.
/*
/*	msg_error_limit() sets the error message count limit, and returns.
/*	the old limit.
/*
/*	msg_error_clear() sets the error message count to zero.
/*
/*	msg_verbose is a global flag that can be set to make software
/*	more verbose about what it is doing. By default the flag is zero.
/*	By convention, a larger value means more noise.
/* REENTRANCY
/* .ad
/* .fi
/*	The msg_info() etc. output routines are protected against
/*	ordinary recursive calls and against re-entry by signal
/*	handlers.
/*
/*	Protection against re-entry by signal handlers is subject
/*	to the following limitations:
/* .IP \(bu
/*	The signal handlers must never return. In other words, the
/*	signal handlers must do one or more of the following: call
/*	_exit(), kill the process with a signal, and permanently block
/*	the process.
/* .IP \(bu
/*	The signal handlers must invoke msg_info() etc. not until
/*	after the msg_XXX_init() functions complete initialization,
/*	and not until after the first formatted output to a VSTRING
/*	or VSTREAM.
/* .IP \(bu
/*	Each msg_cleanup() call-back function, and each Postfix or
/*	system function invoked by that call-back function, either
/*	protects itself against recursive calls and re-entry by a
/*	terminating signal handler, or is called exclusively by the
/*	msg(3) module.
/* .PP
/*	When re-entrancy is detected, the requested output and
/*	optional cleanup operations are skipped. Skipping the output
/*	operations prevents memory corruption of VSTREAM_ERR data
/*	structures, and prevents deadlock on Linux releases that
/*	use mutexes within system library routines such as syslog().
/*	This protection exists under the condition that these
/*	specific resources are accessed exclusively via the msg_info()
/*	etc.  functions.
/* EXCEPTIONS AND NON-PRODUCTION TESTS
/* .ad
/* .fi
/*	The default action for msg_fatal*() and msg_panic() is to
/*	terminate the program. In order to support non-production tests,
/*	the following function implements support to maintain control
/*	over the execution.
/*
/*	msg_set_longjmp_action() accepts a pointer to non-returning
/*	function that will be called by msg_fatal*() and msg_panic(),
/*	with an argument of MSG_LONGJMP_FATAL and MSG_LONGJMP_PANIC,
/*	respectively. Specify a null argument to disable this feature.
/* SEE ALSO
/*	msg_output(3) specify diagnostics disposition
/*	msg_vstream(3) direct diagnostics to VSTREAM.
/*	msg_syslog(3) direct diagnostics to syslog daemon
/* BUGS
/*	Some output functions may suffer from intentional or accidental
/*	record length restrictions that are imposed by library routines
/*	and/or by the runtime environment.
/*
/*	Code that spawns a child process should almost always reset
/*	the cleanup handler. The exception is when the parent exits
/*	immediately and the child continues.
/*
/*	msg_cleanup() may be unsafe in code that changes process
/*	privileges, because the call-back routine may run with the
/*	wrong privileges.
/* LICENSE
/* .ad
/* .fi
/*	The Secure Mailer license must be distributed with this software.
/* AUTHOR(S)
/*	Wietse Venema
/*	IBM T.J. Watson Research
/*	P.O. Box 704
/*	Yorktown Heights, NY 10598, USA
/*
/*	Wietse Venema
/*	porcupine.org
/*--*/

/* System libraries. */

#include <sys_defs.h>
#include <stdlib.h>
#include <stdarg.h>
#include <unistd.h>

/* Application-specific. */

#include "msg.h"
#include "msg_output.h"

 /*
  * Default is verbose logging off.
  */
int     msg_verbose = 0;

 /*
  * Private state.
  */
MSG_LONGJMP_ACTION msg_longjmp_action;
static MSG_CLEANUP_FN msg_cleanup_fn = 0;
static int msg_error_count = 0;
static int msg_error_bound = 13;

 /*
  * The msg_exiting flag prevents us from recursively reporting an error with
  * msg_fatal*() or msg_panic(), and provides a first-level safety net for
  * optional cleanup actions against signal handler re-entry problems. Note
  * that msg_vprintf() implements its own guard against re-entry.
  * 
  * XXX We specify global scope, to discourage the compiler from doing smart
  * things.
  */
volatile int msg_exiting = 0;

/* msg_info - report informative message */

void    msg_info(const char *fmt,...)
{
    va_list ap;

    va_start(ap, fmt);
    vmsg_info(fmt, ap);
    va_end(ap);
}

void    vmsg_info(const char *fmt, va_list ap)
{
    msg_vprintf(MSG_INFO, fmt, ap);
}

/* msg_warn - report warning message */

void    msg_warn(const char *fmt,...)
{
    va_list ap;

    va_start(ap, fmt);
    vmsg_warn(fmt, ap);
    va_end(ap);
}

void    vmsg_warn(const char *fmt, va_list ap)
{
    msg_vprintf(MSG_WARN, fmt, ap);
}

/* msg_error - report recoverable error */

void    msg_error(const char *fmt,...)
{
    va_list ap;

    va_start(ap, fmt);
    vmsg_error(fmt, ap);
    va_end(ap);
}

void    vmsg_error(const char *fmt, va_list ap)
{
    msg_vprintf(MSG_ERROR, fmt, ap);
    if (++msg_error_count >= msg_error_bound)
	msg_fatal("too many errors - program terminated");
}

/* msg_fatal - report error and terminate gracefully */

NORETURN msg_fatal(const char *fmt,...)
{
    va_list ap;

    va_start(ap, fmt);
    vmsg_fatal(fmt, ap);
    /* NOTREACHED */
}

NORETURN vmsg_fatal(const char *fmt, va_list ap)
{
    if (msg_exiting++ == 0) {
	msg_vprintf(MSG_FATAL, fmt, ap);
	if (msg_cleanup_fn)
	    msg_cleanup_fn();
    }
    if (msg_longjmp_action) {
	/* This code is reachable during testing only. */
	msg_exiting = 0;
	msg_longjmp_action(MSG_LONGJMP_FATAL);
    }
    sleep(1);
    /* In case we're running as a signal handler. */
    _exit(1);
}

/* msg_fatal_status - report error and terminate gracefully */

NORETURN msg_fatal_status(int status, const char *fmt,...)
{
    va_list ap;

    va_start(ap, fmt);
    vmsg_fatal_status(status, fmt, ap);
    /* NOTREACHED */
}

NORETURN vmsg_fatal_status(int status, const char *fmt, va_list ap)
{
    if (msg_exiting++ == 0) {
	msg_vprintf(MSG_FATAL, fmt, ap);
	if (msg_cleanup_fn)
	    msg_cleanup_fn();
    }
    if (msg_longjmp_action) {
	/* This code is reachable during testing only. */
	msg_exiting = 0;
	msg_longjmp_action(MSG_LONGJMP_FATAL);
    }
    sleep(1);
    /* In case we're running as a signal handler. */
    _exit(status);
}

/* msg_panic - report error and dump core */

NORETURN msg_panic(const char *fmt,...)
{
    va_list ap;

    va_start(ap, fmt);
    vmsg_panic(fmt, ap);
    /* NOTREACHED */
}

NORETURN vmsg_panic(const char *fmt, va_list ap)
{
    if (msg_exiting++ == 0) {
	msg_vprintf(MSG_PANIC, fmt, ap);
    }
    if (msg_longjmp_action) {
	/* This code is reachable during testing only. */
	msg_exiting = 0;
	msg_longjmp_action(MSG_LONGJMP_PANIC);
    }
    sleep(1);
    abort();					/* Die! */
    /* In case we're running as a signal handler. */
    _exit(1);					/* DIE!! */
}

/* msg_cleanup - specify cleanup routine */

MSG_CLEANUP_FN msg_cleanup(MSG_CLEANUP_FN cleanup_fn)
{
    MSG_CLEANUP_FN old_fn = msg_cleanup_fn;

    msg_cleanup_fn = cleanup_fn;
    return (old_fn);
}

/* msg_error_limit - set error message counter limit */

int     msg_error_limit(int limit)
{
    int     old = msg_error_bound;

    msg_error_bound = limit;
    return (old);
}

/* msg_error_clear - reset error message counter */

void    msg_error_clear(void)
{
    msg_error_count = 0;
}

/* msg_set_longjmp_action() - exception handling */

void    msg_set_longjmp_action(MSG_LONGJMP_ACTION action)
{
    msg_longjmp_action = action;
}
