/*
 * Utility Functions to implement consistent error logging mechanisms.
 * Specification requires that this work on VMS and SunOS (UNIX).  Tested
 * on ULTRIX and AIX as well.
 */

#ifndef	lint
    static char afsid[]	= "$__Header$";
    static char rcsid[]	= "$Id: uderrmsg.c,v 1.1 2000/08/07 23:15:04 emmerson Exp $";
#endif	/* lint not defined */


/*LINTLIBRARY*/

#include "udposix.h"
#include <stdlib.h>
#include <string.h>
/*
 * Due to a bug in gcc 2.4.5 for SunOS 4.1.3, include <stdarg.h> before
 * <stdio.h>.
 */
#include <stdarg.h>
#include <stdio.h>
#include <time.h>
#include "uderrmsg.h"

#include <errno.h>
#ifdef ERRNO_MISSING
    extern int errno;
#endif

#ifndef MAX_CANON
#   define MAX_CANON	255
#endif

extern char	*udstrdup();


/*
 * These subroutines emit no messages unless UD_VERBOSE bit is on.
 * These subroutines call exit() when UD_FATAL bit is on.
 */
static int	ud_error_options	= (UD_FATAL | UD_VERBOSE);


/*
 * Set to the name of the application by the interface routine.
 */
static char	*ud_prog_name	= "<unknown>";


/*
 * Print a time-stamp.
 */

    char*
udtime_stamp()
{
    static char	buf[32];
    time_t	tim	= time((time_t*)NULL);

    if (tim == -1) {
	(void)strcpy(buf, "<time unavailable>");
    } else {
	buf[sizeof(buf)-1]	= 0;
	(void)strftime(buf, sizeof(buf)-1, "%Y-%m-%d %H:%M:%S %Z", 
	    localtime(&tim));
    }

    return buf;
}


/*
 * Print the header of a line (i.e. timp-stamp and application-name).
 */

    static int
print_header()
{
    int		nchar;

    if (isatty(2)) {
	nchar	= fprintf(stderr, "%s: ", ud_prog_name);
    } else {
	nchar	= fprintf(stderr, "(%s) %s: ", udtime_stamp(), ud_prog_name);
    }

    return nchar;
}


/*
 * Set the mode-bits of this module.
 */

    int
uderrmode(mode)
    const int	mode;
{
    int		oldmode	= ud_error_options;

    ud_error_options	= mode;

    return oldmode;
}


/*
 * Set the name of the application.
 */

    char*
uderrname(name)
    const char	*name;
{
    char	*oldname	= ud_prog_name;

    if (name != NULL)
	ud_prog_name	= udstrdup(name);

    return oldname;
}


#ifdef vms
/*
 * On the vms system, when a system error occurs which is not
 * mapped into the unix styled errno values, errno is set EVMSERR
 * and a VMS error code is set in vaxc$errno.
 * This routine prints the systems message associated with status return
 * from a system services call.
 */

#include <descrip.h>
#include <ssdef.h>

    static int
vmserror1(int status)
{
    short	msglen;
    char	msgbuf[256];
    $DESCRIPTOR(message, msgbuf);
    register	ret	= SYS$GETMSG(status, &msglen, &message, 15, 0);
    
    switch(ret) {
    case SS$_BUFFEROVF:
    case SS$_NORMAL:
	ret	= fprintf(stderr, "%.*s\n", msglen, msgbuf);
	break;
    default:
	break;
    }

    return ret;
}
#endif /* vms */


/*
 * Log a SYSTEM error (va_list version).  Return the number of
 * characters written or negative if an error occurs.
 * Use where you would want to call perror(3).
 */
    int
#ifndef UD_NO_STDARG
udverror(const char *fmt, va_list alist)
#else
udverror(fmt, alist)
    const char	*fmt;
    va_list	alist;
#endif
{
    int		nchar	= 0;
    int		nc;

    if (ud_error_options & UD_VERBOSE) {
	int	errnum	= errno;		/* save real one */
	char	*errstr	= strerror(errnum);

	if ((nc = print_header()) >= 0) {
	    nchar	+= nc;
	    if ((nc = vfprintf(stderr, fmt, alist)) >= 0) {
		nchar	+= nc;

#		ifdef vms
		if (errnum == EVMSERR) {
		    if ((nc = fputs(': ', stderr)) != EOF) {
			nchar	+= 2;
			if ((nc = vmserror1(vaxc$errno)) >= 0)
			    nchar	+= nc;
		    }
		} else
#		endif /* vms defined above */

		if (errstr == NULL) {
		    if ((nc = fputc('\n', stderr) != EOF))
			++nchar;
		} else {
		    if ((nc = fprintf(stderr, ": %s (errno=%d)\n", 
				      errstr, errnum)) >= 0)
			nchar	+= nc;
		}

		if (nc >= 0)
		    nc	= fflush(stderr);

		errno	= errnum;
	    }
	}

	if (nc < 0)
	    nchar	= -1;
    }

    if (ud_error_options & UD_FATAL)
	exit(ud_error_options);

    return nchar;
}


/*
 * Log a SYSTEM error (variadic version).
 * Use where you would want to call perror(3).
 * For example:
 *         uderror("shutting down");
 *	   uderror("can't open %s", file_name);
 *         uderror("process %d in state %s",pid,state);
 */
    void
#if defined(UD_STDARG) || !defined(UD_NO_STDARG)
uderror(const char *fmt, ...)
#else
    /*VARARGS1*/
uderror(fmt, va_alist)
    const char	*fmt;
    va_dcl
#endif
{
    va_list	alist;

    UD_VA_START(alist, fmt);
    (void) udverror(fmt, alist);
    va_end(alist);
}


/*
 * Create an message-format containing a routine name.
 */
    static char*
tagfmt(name, fmt)
    const char	*name;
    const char	*fmt;
{
    char	buf[MAX_CANON];

    if (name != NULL && *name != 0) {
	(void) strncpy(buf, name, sizeof(buf));
	buf[sizeof(buf)-1]	= 0;
	(void) strncat(buf, "(): ", sizeof(buf) - strlen(buf) - 1);
    }

    (void) strncat(buf, fmt, sizeof(buf) - strlen(buf) - 1);

    return buf;
}


/*
 * Log a system error (with the name of the routine).
 * Returns error-code argument.
 * Example:
 *	return udsyserr(-1, "newfoo", "couldn't allocate %d bytes", nchr);
 */
    int
#if defined(UD_STDARG) || !defined(UD_NO_STDARG)
udsyserr(int code, const char *name, const char *fmt, ...)
#else
    /*VARARGS1*/
udsyserr(code, name, fmt, va_alist)
    int		code;
    const char	*name;
    const char	*fmt;
    va_dcl
#endif
{
    va_list	alist;

    UD_VA_START(alist, fmt);
    uderror(tagfmt(name, fmt), alist);
    va_end(alist);

    return code;
}


/*
 * Log a user error (va_list version).  Return the number of characters written
 * or negative if an error occurs.
 * Use for logging error conditions which are not system errors.
 */
    int
#if defined(UD_STDARG) || !defined(UD_NO_STDARG)
udvadvise(const char *fmt, va_list alist)
#else
udvadvise(fmt, alist)
    const char	*fmt;
    va_list	alist;
#endif
{
    int		nchar	= 0;
    int		nc;

    if (ud_error_options & UD_VERBOSE) {
	if ((nc = print_header()) >= 0) {
	    nchar	+= nc;
	    if ((nc = vfprintf(stderr, fmt, alist)) >= 0) {
		nchar	+= nc;
		if ((nc = fputc('\n', stderr) != EOF)) {
		    ++nchar;
		    nc	= fflush(stderr);
		}
	    }
	}

	if (nc < 0)
	    nchar	= -1;
    }

    if (ud_error_options & UD_FATAL)
	exit(ud_error_options);

    return nchar;
}


/*
 * Log a user error (variadic version).
 * Use for logging error conditions which are not system errors.
 * For example:
 *         udadvise("just advice");
 *         udadvise("%d is not a valid id", id);
 */
    void
#if defined(UD_STDARG) || !defined(UD_NO_STDARG)
udadvise(const char *fmt, ...)
#else /* !UD_STDARG */
    /*VARARGS1*/
udadvise(fmt, va_alist)
    const char	*fmt;
    va_dcl
#endif /* !UD_STDARG */
{
    va_list	alist;

    UD_VA_START(alist ,fmt);
    udvadvise(fmt, alist);
    va_end(alist);
}


/*
 * Log a user error (with the name of the routine).
 * Returns error-code argument.
 * Example:
 *	return udusererr(-1, "foo", "%d is an invalid id", id);
 */
    int
#if defined(UD_STDARG) || !defined(UD_NO_STDARG)
udusererr(int code, const char *name, const char *fmt, ...)
#else
    /*VARARGS1*/
udusererr(code, name, fmt, va_alist)
    int		code;
    const char	*name;
    const char	*fmt;
    va_dcl
#endif
{
    va_list	alist;

    UD_VA_START(alist, fmt);
    udvadvise(tagfmt(name, fmt), alist);
    va_end(alist);

    return code;
}
