/*
 * NAME:
 *	accumlog - append info to log entry
 *
 * SYNOPSIS:
 *	int	accumlog(level, fmt, ...)
 *	void	accumlog_syslog(level, fmt, ...)
 *
 * DESCRIPTION:
 *	
 *	If "fmt" is null we flush any accumulated log to syslog, if
 *	"level" is zero we use the value from the previous call.  If
 *	"fmt" is NULL and "level" is < 0 we simply trash any
 *	accumulated log, otherwise we just append to an existing
 *	entry.
 *
 *	accumlog_syslog is intended as a replacement for syslog calls
 *	via a simple cpp define.  Its only role is to flush accumlog if
 *	"level" is greater than the last level passed to accumlog().
 *	
 * AUTHOR:
 *	Simon J. Gerraty <sjg@quick.com.au>
 */
/*
 *	@(#)Copyright (c) 1998 Simon J. Gerraty.
 *	
 *	This is free software.	It comes with NO WARRANTY.
 *	Permission to use, modify and distribute this source code 
 *	is granted subject to the following conditions.
 *	1/ that the above copyright notice and this notice 
 *	are preserved in all copies and that due credit be given 
 *	to the author.	
 *	2/ that any changes to this code are clearly commented 
 *	as such so that the author does not get blamed for bugs 
 *	other than his own.
 *	
 *	Please send copies of changes and bug-fixes to:
 *	sjg@quick.com.au
 */
#ifndef lint
static char RCSid[] = "$Id: accumlog.c,v 1.4 2001/06/26 11:00:17 sjg Exp $";
#endif

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include	<stdio.h>
#if defined(__STDC__) || defined(__cplusplus)
#include <stdarg.h>
#else
#include <varargs.h>
#endif
#include	<syslog.h>
#include	<sys/types.h>
#include	<string.h>
#ifdef HAVE_MALLOC_H
# include	<malloc.h>
#else
extern char *malloc(), *realloc();
#endif

#ifdef UNIT_TEST
# define LOG_HUNK 10
static int fake_err = 0;
# include <unistd.h>
#endif

#ifndef LOG_HUNK
# define LOG_HUNK 128
#endif
#ifndef MAX
# define MAX(a, b) (((a) < (b)) ? (b) : (a))
#endif

#undef syslog

/*
 * We keep track of the last non-zero level given to accumlog()
 * so that accumlog_syslog() can call accumlog(0, 0) to flush.
 */
static int accumlog_level = 0;

int
#ifdef __STDC__
accumlog(int level, const char *fmt, ...) {
#else
accumlog(va_alist)
	va_dcl	
{
	int level;
	char *fmt;
#endif
	va_list va;
	static char *log = 0;
	static int lsz = 0;
	static int lx = 0;
	char *cp;
	int i, x, nsz, space = 0;
		
#ifdef __STDC__
	va_start(va, fmt);
#else
	va_start(va);
	level = va_arg(va, int);
	fmt = va_arg(va, char *);
#endif
	if (log == 0) {
		if (fmt == NULL)
			return 0;	/* nothing to do */
		
		lsz = 2 * LOG_HUNK;
		if (
#ifdef UNIT_TEST
		    fake_err == 1 ||
#endif
		    (log = (char *) malloc(lsz)) == 0) {
			syslog(LOG_ERR, "accumlog: malloc(%d): %m", lsz);
			lsz = 0;
#ifdef UNIT_TEST
			++fake_err;
#endif
		}
	}
	if (level == 0)
		level = accumlog_level;	/* handy for flushing */
	else if (level > 0)
		accumlog_level = level;
	if (fmt == 0) {			/* flush */
		if (lx > 0 && level > 0 && log != NULL) {
			syslog(level, "%s", log);
			space = lx;
		}
		lx = 0;
		accumlog_level = 0;	/* no longer valid */
		va_end(va);
		return space;		/* how much logged */
	}
	if (log == NULL) {		/* not much we can do */
		vsyslog(level, fmt, va); /* XXX yes? */
		va_end(va);
		return -1;
	}
	do {
		space = lsz - lx;
		x = vsnprintf(&log[lx], space, fmt, va);
		if (x < 0) {
			syslog(LOG_ERR, "accumlog: vsnprintf(\"%s\", ...): %m", fmt);
			lx = 0;			/* lose */
		}
		if (x > 0 && (i = x + (LOG_HUNK / 2)) > space) {
			nsz = lsz + MAX(i, LOG_HUNK);
			if (
#ifdef UNIT_TEST
			    fake_err == 2 ||
#endif
			    (cp = realloc(log, nsz)) == 0) {
				syslog(LOG_ERR, "accumlog: realloc(%d): %m", nsz);
#ifdef UNIT_TEST
				++fake_err;
#endif
				space = lx + x;
				if (lx > 0) {
					syslog(level, "%.*s...", lx, log);
					lx = 0;
				}
				vsyslog(level, fmt, va);
				return -space;
			}
			/*
			 * realloc succeeded.
			 */
			lsz = nsz;
			log = cp;
		}
	} while (x > 0 && x > space) ;

	if (x > 0) {
		lx += x;
		if (log[lx - 1] == '\n')
			lx--;
	}
	
	va_end(va);
	return lx;
}

/*
 * Just a wrapper for vsyslog(), ensures that accumlog is
 * flushed first if level is greater than accumlog_level.
 */
void
#ifdef __STDC__
accumlog_syslog(int level, const char *fmt, ...) {
#else
accumlog_syslog(va_alist)
	va_dcl	
{
	int level;
	char *fmt;
#endif
	va_list va;
		
#ifdef __STDC__
	va_start(va, fmt);
#else
	va_start(va);
	level = va_arg(va, int);
	fmt = va_arg(va, char *);
#endif
	if (level > accumlog_level)
		accumlog(0, 0);		/* flush accumlog() */
	vsyslog(level, fmt, va);
	va_end(va);
}

#ifdef UNIT_TEST
int
main(argc, argv)
	int argc;
	char **argv;
{
	int i;
	
	while ((i = getopt(argc, argv, "e")) != EOF) {
		switch (i) {
		case 'e':
			fake_err = 1;
			break;
		}
	}
	
	openlog("accumlog", 0, LOG_LOCAL0);
	accumlog(LOG_INFO, "PID=%d\n", getpid()); /* should lose the \n */

	for (i = optind; optind < argc; ++optind) {
		accumlog(LOG_INFO, ", argv[%d]='%s'", optind - i, argv[optind]);
	}
	accumlog_syslog(LOG_INFO, "Done.");
	exit(0);
}
#endif

