
/*
 * Copyright (c) 2004
 * All rights reserved.
 * 
 */

#include <sys/types.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <sys/uio.h>

#include <netinet/in.h>

#include <stdlib.h>
#include <stddef.h>
#include <stdio.h>
#include <syslog.h>
#include <assert.h>
#include <string.h>
#include <stdarg.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <regex.h>
#include <pthread.h>

#include "alog.h"
#include "circfile.h"

#ifndef MAX
#define MAX(x,y) ((x>y) ? x : y)
#endif
#ifndef MIN
#define MIN(x,y) ((x<y) ? x : y)
#endif

#define REG_STARTEND    0

/* Last message timeout functions */
#define LASTMSG_TIMEOUT_INITIAL	5
#define LASTMSG_TIMEOUT_1(t, d)	((t) + (d) + 1 / 2)
#define LASTMSG_TIMEOUT_2(t, d)	((t) + (d) + 1 / 2)
#define LASTMSG_TIMEOUT_3(t)	(t)

#define LASTMSG_REPEATED_FMT	"last message repeated %u times"

/* Misc defs */
#define SYSLOG_PORT		514	/* syslogd udp port */
#define MAX_NAME		64	/* max identifier length */
#define EST_ENTRY_SIZE		100	/* estimated avg. size per entry */

enum alog_type {
   ALOG_NONE = 0,		/* not initialized */
   ALOG_NULL,			/* log to nowhere */
   ALOG_STDERR,			/* log to stderr */
   ALOG_SYSLOG_LOCAL,		/* log to local machine syslog */
   ALOG_SYSLOG_REMOTE		/* log to remote machine syslog */
};

/* Current alog channel (thread-specific variable) */
struct alog_curchan {
   int channel;
};

/* Stolen from syslog.h */
struct nameval {
  const char *name;
  int        val;
};

/* 'Last message' state for a channel */
struct alog_lastmsg {
   char   *msg;		/* previously logged message */
   int    sev;		/* severity for 'msg' */
   time_t when;		/* timestamp for 'msg' */
   u_int  repeat;	/* number of unlogged repeats */
   u_int  timeout;	/* timeout for logging repeat */
   time_t timer_expiry;	/* timer running & expiry */
};

/* Structure describing one channel */
struct alog_channel {
   char           name[MAX_NAME];	/* local syslog(3) identifier */
   struct circfile     *circfile;	/* history circfile, or NULL */
   enum alog_type      type;		/* channel configuration type */
   int                 sock;		/* remote syslog(3) socket */
   int                 facility;	/* channel facility */
   int                 min_severity;	/* min channel severity */
   u_char              debug;		/* debug enabled flag */
   pthread_mutex_t     mutex;		/* mutex, if not ALOG_NONE */
   struct alog_lastmsg last;		/* 'last message' info */
};

#ifndef LOG_MAKEPRI
#define    LOG_MAKEPRI(fac, pri)   (((fac) << 3) | (pri))
#endif
#ifndef LOG_PRI
#define    LOG_PRI(p)      ((p) & LOG_PRIMASK)
#endif


/*
 * Internal variables
 */
static struct		alog_channel alog_channels[ALOG_MAX_CHANNELS];
static pthread_key_t	alog_current_channel;

/* Stolen from syslog.h */
static struct	nameval prioritynames[] = {
   { "alert",	LOG_ALERT,	},
   { "crit",	LOG_CRIT,	},
   { "debug",	LOG_DEBUG,	},
   { "emerg",	LOG_EMERG,	},
   { "err",	LOG_ERR,	},
   { "error",	LOG_ERR,	},	/* DEPRECATED */
   { "info",	LOG_INFO,	},
   { "notice",	LOG_NOTICE,	},
   { "panic", 	LOG_EMERG,	},	/* DEPRECATED */
   { "warn",	LOG_WARNING,	},	/* DEPRECATED */
   { "warning",	LOG_WARNING,	},
   { NULL,		-1,		}
};

/* Stolen from syslog.h */
static struct	nameval facilitynames[] = {
   { "auth",	LOG_AUTH,	},
#ifdef LOG_AUTHPRIV
   { "authpriv",	LOG_AUTHPRIV,	},
#endif
   { "cron", 	LOG_CRON,	},
   { "daemon",	LOG_DAEMON,	},
#ifdef LOG_FTP
   { "ftp",	LOG_FTP,	},
#endif
   { "kern",	LOG_KERN,	},
   { "lpr",	LOG_LPR,	},
   { "mail",	LOG_MAIL,	},
   { "news",	LOG_NEWS,	},
#ifdef LOG_NTP
   { "ntp",	LOG_NTP,	},
#endif
#ifdef LOG_SECURITY
   { "security",	LOG_SECURITY,	},
#endif
   { "syslog",	LOG_SYSLOG,	},
   { "user",	LOG_USER,	},
   { "uucp",	LOG_UUCP,	},
   { "local0",	LOG_LOCAL0,	},
   { "local1",	LOG_LOCAL1,	},
   { "local2",	LOG_LOCAL2,	},
   { "local3",	LOG_LOCAL3,	},
   { "local4",	LOG_LOCAL4,	},
   { "local5",	LOG_LOCAL5,	},
   { "local6",	LOG_LOCAL6,	},
   { "local7",	LOG_LOCAL7,	},
   { NULL,		-1,		}
};

/*
 * Internal functions
 */
static void	alog_write(struct alog_channel *ch,
			int sev, time_t when, const char *msg);
static void	alog_flush_lastmsg(struct alog_channel *ch, u_int timeout);
static void	alog_last_check(struct alog_channel *ch);
static int	alog_get_channel(void);
static struct	alog_curchan *alog_get_current_channel(int create);
static void	alog_init_current_channel(void);
static void	alog_curchan_destroy(void *arg);
static int      vasprintf(char **ret, const char *format, va_list ap);
static int      asprintf(char **ret, const char *format, ...);

/*
 * Initialize or reconfigure a logging channel.
 */
int
alog_configure(int channel, const struct alog_config *conf)
{
	struct alog_channel *const ch = &alog_channels[channel];
	struct sockaddr_in sin;
	int init_mutex = 0;
	int esave;
	int debug;

	/* Sanity check */
	if (channel < 0 || channel >= ALOG_MAX_CHANNELS) {
		errno = EINVAL;
		return (-1);
	}

	/* If already initialized, shut it down */
	if (ch->type != ALOG_NONE)
		alog_shutdown(channel);

	/* Initialize channel */
	debug = ch->debug;
	memset(ch, 0, sizeof(*ch));
	ch->sock = -1;
	ch->debug = debug;
	if (conf->name != NULL)
		strncpy(ch->name, conf->name, sizeof(ch->name));
	ch->facility = (ch->debug || conf->facility == NULL) ?
	    -1 : alog_facility(conf->facility);
	ch->min_severity = conf->min_severity;

	/* Open circfile */
	if (conf->histlen > 0) {
		if ((ch->circfile = circfile_open(conf->path, 0,
		    conf->histlen, conf->histlen * EST_ENTRY_SIZE)) == NULL)
			goto fail;
	}

	/* Initialize mutex */
	if ((errno = pthread_mutex_init(&ch->mutex, NULL)) != 0)
		goto fail;
	init_mutex = 1;

	/* Handle stderr case */
	if (ch->facility == -1) {
		ch->type = ALOG_STDERR;
		return (0);
	}

	/* Handle NULL case */
	if (conf->name == NULL) {
		ch->type = ALOG_NULL;
		return (0);
	}

	/* Handle local syslog case */
	if (conf->remote_server.s_addr == 0) {
		ch->type = ALOG_SYSLOG_LOCAL;
		return (0);
	}

	/* Handle remote syslog case */
	if ((ch->sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1)
		goto fail;
	(void)fcntl(ch->sock, F_SETFD, 1);
	if (shutdown(ch->sock, SHUT_RD) == -1)
		goto fail;
	memset(&sin, 0, sizeof(sin));
	sin.sin_family = AF_INET;
	sin.sin_port = htons(SYSLOG_PORT);
	sin.sin_addr = conf->remote_server;
	if (connect(ch->sock, (struct sockaddr *)&sin, sizeof(sin)) == -1)
		goto fail;
	ch->type = ALOG_SYSLOG_REMOTE;
	return (0);

fail:
	/* Clean up after failure */
	esave = errno;
	circfile_close(&ch->circfile);			/* ok if null */
	if (ch->sock != -1) {
		(void)close(ch->sock);
		ch->sock = -1;
	}
	if (init_mutex)
		pthread_mutex_destroy(&ch->mutex);
	errno = esave;
	return (-1);
}

/*
 * Shutdown one logging channel.
 */
int
alog_shutdown(int channel)
{
	struct alog_channel *const ch = &alog_channels[channel];

	/* Sanity check */
	if (channel < 0 || channel >= ALOG_MAX_CHANNELS) {
		errno = EINVAL;
		return (-1);
	}

	/* Cancel timer */
	ch->last.timer_expiry = 0;

	/* Flush any repeated message */
	alog_flush_lastmsg(ch, 0);

	/* Shut down channel */
	switch (ch->type) {
	case ALOG_SYSLOG_REMOTE:
		(void)close(ch->sock);
		ch->sock = -1;
		pthread_mutex_destroy(&ch->mutex);
		break;
	case ALOG_SYSLOG_LOCAL:
		closelog();
		pthread_mutex_destroy(&ch->mutex);
		break;
	case ALOG_NULL:
	case ALOG_STDERR:
		pthread_mutex_destroy(&ch->mutex);
		break;
	case ALOG_NONE:
		break;
	}
	ch->type = ALOG_NONE;

	/* Free saved last message */
	free(ch->last.msg);
	ch->last.msg = NULL;

	/* Close circfile */
	circfile_close(&ch->circfile);			/* ok if null */

	/* Done */
	return (0);
}

/*
 * Get current channel.
 */
static int
alog_get_channel(void)
{
	struct alog_curchan *cc;

	if ((cc = alog_get_current_channel(0)) == NULL)
		return (0);
	return (cc->channel);
}

/*
 * Set active channel.
 */
int
alog_set_channel(int channel)
{
	struct alog_curchan *cc;

	/* Sanity check */
	if (channel < 0 || channel >= ALOG_MAX_CHANNELS) {
		errno = EINVAL;
		return (-1);
	}

	/* Compare with current channel */
	if ((cc = alog_get_current_channel(channel != 0)) == NULL) {
		if (channel != 0)
			return (-1);
		return (0);
	}
	if (channel == cc->channel)
		return (0);

	/* Set to new channel, even if it's uninitialized */
	cc->channel = channel;

	/* Done */
	return (0);
}

/*
 * Get current channel per-thread variable.
 */
static struct alog_curchan *
alog_get_current_channel(int create)
{
	static pthread_once_t init_channel_once = {PTHREAD_ONCE_INIT};
	struct alog_curchan *cc;

	/* Initialize per-thread variable (once) */
	if ((errno = pthread_once(&init_channel_once,
	    alog_init_current_channel)) != 0) {
		fprintf(stderr, "%s: %s: %s\n",
		    "alog", "pthread_once", strerror(errno));
		return (NULL);
	}

	/* Get instance for this thread; create if necessary */
	if ((cc = pthread_getspecific(alog_current_channel)) == NULL) {
		if (!create)
			return (NULL);
		if ((cc = malloc(sizeof(*cc))) == NULL) {
			fprintf(stderr, "%s: %s: %s\n",
			    "alog", "malloc", strerror(errno));
			return (NULL);
		}
		cc->channel = 0;
		if ((errno = pthread_setspecific(alog_current_channel,
		    cc)) != 0) {
			fprintf(stderr, "%s: %s: %s\n",
			    "alog", "pthread_setspecific", strerror(errno));
			free(cc);
			return (NULL);
		}
	}

	/* Return current channel */
	return (cc);
}

/*
 * Initialize "current channel" per-thread variable.
 */
static void
alog_init_current_channel(void)
{
	int err;

	if ((err = pthread_key_create(&alog_current_channel,
	    alog_curchan_destroy)) != 0) {
		fprintf(stderr, "%s: %s: %s\n",
		    "alog", "pthread_key_create", strerror(err));
		assert(0);
	}
}

/*
 * Destroy an instance of the per-thread current channel variable.
 */
static void
alog_curchan_destroy(void *arg)
{
	struct alog_curchan *const cc = arg;

	free(cc);
}

/*
 * Enable debugging on a channel.
 */
void
alog_set_debug(int channel, int enabled)
{
	struct alog_channel *const ch = &alog_channels[channel];

	ch->debug = !!enabled;
}

/*
 * Log something to the currently selected channel. Preserves errno.
 */
void
alog(int sev, const char *fmt, ...)
{
	va_list args;

	va_start(args, fmt);
	valog(sev, fmt, args);
	va_end(args);
}

/*
 * Log something to the currently selected channel. Preserves errno.
 */
void
valog(int sev, const char *fmt, va_list args)
{
	struct alog_channel *const ch = &alog_channels[alog_get_channel()];
	const int errno_save = errno;
	char fmtbuf[1024];
	time_t now;
	char *msg;
	int r;

	/* Lock channel */
	if (ch->type != ALOG_NONE) {
		r = pthread_mutex_lock(&ch->mutex);
		assert(r == 0);
	}

	/* Check last message timer */
	alog_last_check(ch);

	/* Filter out unwanted verbosity */
	if (ch->type != ALOG_NONE && sev > ch->min_severity)
		goto done;

	/* Allow for things like ``alog(LOG_DEBUG + 1, ...)'' */
	if (sev >= LOG_DEBUG)
		sev = LOG_DEBUG;

	/* Build the message */
	alog_expand(fmt, errno_save, fmtbuf, sizeof(fmtbuf));
	vasprintf(&msg, fmtbuf, args);
	if (msg == NULL) {
		fprintf(stderr, "%s: %s\n", "valog", strerror(errno));
		goto done;
	}

	/* Get current time */
	now = time(NULL);

	/* If not configured, don't do the repeated messages thing */
	if (ch->type == ALOG_NONE) {
		alog_write(ch, sev, now, msg);
		free(msg);
		goto done;
	}

	/* Handle repeated messages */
	if (ch->last.msg == NULL || strcmp(msg, ch->last.msg) != 0) {
		alog_flush_lastmsg(ch, LASTMSG_TIMEOUT_INITIAL);
		free(ch->last.msg);
		ch->last.msg = msg;
		ch->last.sev = sev;
		ch->last.when = now;
		alog_write(ch, sev, now, msg);
	} else {
		time_t delay;

		delay = MAX(now - ch->last.when, 0);
		if (++ch->last.repeat == 1) {
			ch->last.timeout = LASTMSG_TIMEOUT_1(
			    ch->last.timeout, delay);
		} else {
			ch->last.timeout = LASTMSG_TIMEOUT_2(
			    ch->last.timeout, delay);
		}
		ch->last.when = now;
		ch->last.timer_expiry = now + ch->last.timeout;
		free(msg);
	}

done:
	/* Unlock channel and restore errno */
	if (ch->type != ALOG_NONE) {
		r = pthread_mutex_unlock(&ch->mutex);
		assert(r == 0);
	}
	errno = errno_save;
}

/*
 * Check for expiration of 'last mesasge' timer.
 *
 * This assumes the channel is locked.
 */
static void
alog_last_check(struct alog_channel *ch)
{
	/* Is the timer "running"? */
	if (ch->last.timer_expiry == 0)
		return;

	/* Has the timer "expired"? */
	if (ch->last.timer_expiry > time(NULL))
		return;

	/* "Stop" timer */
	ch->last.timer_expiry = 0;

	/* Flush last message */
	alog_flush_lastmsg(ch, LASTMSG_TIMEOUT_3(ch->last.timeout));
}

/*
 * Log repeated message(s).
 *
 * This assumes the channel is locked.
 */
static void
alog_flush_lastmsg(struct alog_channel *ch, u_int timeout)
{
	switch (ch->last.repeat) {
	case 0:
		break;
	case 1:
		if (ch->last.msg != NULL) {
			alog_write(ch, ch->last.sev,
			    ch->last.when, ch->last.msg);
		}
		break;
	default:
	    {
		char buf[sizeof(LASTMSG_REPEATED_FMT) + 32];

		snprintf(buf, sizeof(buf),
		    LASTMSG_REPEATED_FMT, ch->last.repeat);
		alog_write(ch, ch->last.sev, ch->last.when, buf);
		break;
	    }
	}
	ch->last.repeat = 0;
	ch->last.timeout = timeout;
	ch->last.timer_expiry = 0;
}

/*
 * Write a message to the log.
 *
 * This assumes the channel is locked.
 */
static void
alog_write(struct alog_channel *ch, int sev, time_t when, const char *msg)
{
	/* Log it to wherever */
	switch (ch->type) {
	case ALOG_NONE:
	case ALOG_STDERR:
		fprintf(stderr, "%s: %s\n",
		    alog_severity_name(LOG_PRI(sev)), msg);
		break;
	case ALOG_NULL:
		break;
	case ALOG_SYSLOG_LOCAL:
		openlog(ch->name, 0, ch->facility);	/* XXX race condition */
		syslog(sev, "%s", msg);			/* XXX race condition */
		break;
	case ALOG_SYSLOG_REMOTE:
	    {
		char ascbuf[32];
		struct tm tm;
		char *netmsg;
		int len;

		if (ch->sock == -1)
			break;
		gmtime_r(&when, &tm);
/*#if defined(__sun__) && !defined(_POSIX_PTHREAD_SEMANTICS)*/
#if defined(__SUNPRO_C) 
		asctime_r(&tm, ascbuf, sizeof(ascbuf));
#else
		asctime_r(&tm, ascbuf);
#endif
		len = asprintf(&netmsg, "<%d>%.15s %s",
		    LOG_MAKEPRI(ch->facility, sev), ascbuf + 4, msg);
		if (netmsg != NULL)
			write(ch->sock, netmsg, len);
		free(netmsg);
		break;
	    }
	}

	/* Save message in history buffer */
	if (ch->circfile != NULL) {
		struct alog_entry *ent;
		int mlen;

		/* Setup entry */
		mlen = strlen(msg) + 1;
		if ((ent = malloc(sizeof(*ent) + mlen)) == NULL) {
			fprintf(stderr, "%s: %s: %s\n",
			    "alog_write", "malloc", strerror(errno));
			return;
		}
		ent->when = when;
		ent->sev = LOG_PRI(sev);
		memcpy(ent->msg, msg, mlen);

		/* Add to history file */
		if (circfile_put(ch->circfile, ent, sizeof(*ent) + mlen) == -1) {
			fprintf(stderr, "%s: %s: %s\n",
			    "alog_write", "circfile_put", strerror(errno));
		}
		free(ent);
	}
}

/*
 * Get channel history
 */
int
alog_get_history(int channel, int min_sev, int max_num, time_t max_age,
	const regex_t *preg, struct alog_history *list)
{
	struct alog_channel *const ch = &alog_channels[channel];
	struct alog_entry *copy;
	regmatch_t pmatch;
	u_int num_ent;
	int rtn = -1;
	time_t now;
	int i, j;
	u_int num;
	int r;

	/* Sanity check */
	if (channel < 0 || channel >= ALOG_MAX_CHANNELS) {
		errno = EINVAL;
		return (-1);
	}

	/* Lock channel */
	if (ch->type != ALOG_NONE) {
		r = pthread_mutex_lock(&ch->mutex);
		assert(r == 0);
	}

	/* Check last message timer */
	alog_last_check(ch);

	/* Initialize array */
	memset(list, 0, sizeof(*list));

	/* Nothing to return? */
	if (ch->circfile == NULL
	    || max_num == 0
	    || (num_ent = circfile_num_entries(ch->circfile)) == 0) {
		rtn = 0;
		goto done;
	}

	/* Get current time */
	now = time(NULL);

#define MATCH(sev, when, msg, msglen)					\
	(sev <= min_sev && (now - when) <= max_age && (preg == NULL	\
	  || (pmatch.rm_so = 0, pmatch.rm_eo = msglen,			\
	    regexec(preg, msg, 0, &pmatch, REG_STARTEND) == 0)))

	/* Count how many entries we'll be returning */
	num = 0;
	if (ch->last.repeat > 0
	    && ch->last.msg != NULL
	    && MATCH(ch->last.sev, ch->last.when,
	      ch->last.msg, strlen(ch->last.msg)))
		num++;
	for (i = -1; num < max_num && i >= -num_ent; i--) {
		const struct alog_entry *ent;
		int len;

		/* Get entry, check severity and age */
		if ((ent = circfile_get(ch->circfile, i, &len)) != NULL
		    && len >= sizeof(*ent) + 1
		    && MATCH(ent->sev, ent->when,
		      ent->msg, len - sizeof(*ent) - 1))
			num++;
	}
	if (num == 0) {
		rtn = 0;
		goto done;
	}

	/* Allocate array of pointers */
	if ((list->elems = malloc(num * sizeof(*list->elems))) == NULL)
		goto done;

	/* Fill array, starting with the most recent first */
	j = num;
	if (ch->last.repeat > 0
	    && ch->last.msg != NULL
	    && MATCH(ch->last.sev, ch->last.when,
	      ch->last.msg, strlen(ch->last.msg))) {
		char buf[sizeof(LASTMSG_REPEATED_FMT) + 32];
		const char *s;

		if (ch->last.repeat > 1) {
			snprintf(buf, sizeof(buf),
			    LASTMSG_REPEATED_FMT, ch->last.repeat);
			s = buf;
		} else
			s = ch->last.msg;
		if ((copy = malloc(sizeof(*copy) + strlen(s) + 1)) != NULL) {
			copy->when = ch->last.when;
			copy->sev = ch->last.sev;
			strcpy(copy->msg, s);
			list->elems[--j] = copy;
		}
	}
	for (i = -1; j > 0 && i >= -num_ent; i--) {
		const struct alog_entry *ent;
		int len;

		/* Copy it if it matches */
		if ((ent = circfile_get(ch->circfile, i, &len)) != NULL
		    && len >= sizeof(*ent) + 1
		    && MATCH(ent->sev, ent->when,
		      ent->msg, len - sizeof(*ent) - 1)) {
			if ((copy = malloc(len)) != NULL) {
				memcpy(copy, ent, len - 1);
				((char *)copy)[len - 1] = '\0';	/* safety */
				list->elems[--j] = copy;
			}
		}
	}

	/* Collapse entries dropped because of memory problems or whatever */
	if (j > 0) {
		num -= j;
		memcpy(list->elems, list->elems + j,
		    num * sizeof(*list->elems));
	}
	list->length = num;
	rtn = 0;

done:
	/* Unlock channel and return */
	if (ch->type != ALOG_NONE) {
		r = pthread_mutex_unlock(&ch->mutex);
		assert(r == 0);
	}
	return (rtn);
}

/*
 * Clear log history.
 */
int
alog_clear_history(int channel)
{
	struct alog_channel *const ch = &alog_channels[channel];
	int r;

	/* Sanity check */
	if (channel < 0 || channel >= ALOG_MAX_CHANNELS) {
		errno = EINVAL;
		return (-1);
	}

	/* Lock channel */
	if (ch->type != ALOG_NONE) {
		r = pthread_mutex_lock(&ch->mutex);
		assert(r == 0);
	}

	/* Reset last message state */
	ch->last.timer_expiry = 0;
	free(ch->last.msg);
	memset(&ch->last, 0, sizeof(ch->last));

	/* Zero out circfile */
	if (ch->circfile != NULL)
		circfile_trim(ch->circfile, 0);

	/* Unlock channel */
	if (ch->type != ALOG_NONE) {
		r = pthread_mutex_unlock(&ch->mutex);
		assert(r == 0);
	}

	/* Done */
	return (0);
}

/*
 * Decode syslog facility name
 */
int
alog_facility(const char *name)
{
	int i;

	for (i = 0; facilitynames[i].name != NULL; i++) {
		if (strcmp(name, facilitynames[i].name) == 0)
			return (facilitynames[i].val);
	}
	return (-1);
}

/*
 * Decode syslog facility name
 */
const char *
alog_facility_name(int facility)
{
	int i;

	for (i = 0; facilitynames[i].name != NULL; i++) {
		if (facilitynames[i].val == facility)
			return (facilitynames[i].name);
	}
	return (NULL);
}

/*
 * Decode syslog severity name
 */
int
alog_severity(const char *name)
{
	char *eptr;
	int i;

	for (i = 0; prioritynames[i].name != NULL; i++) {
		if (strcmp(name, prioritynames[i].name) == 0)
			return (prioritynames[i].val);
	}
	i = (int)strtol(name, &eptr, 0);
	if (*name != '\0' && *eptr == '\0')
		return (i);
	return (-1);
}

/*
 * Decode syslog severity name
 */
const char *
alog_severity_name(int severity)
{
	int i;

	for (i = 0; prioritynames[i].name != NULL; i++) {
		if (prioritynames[i].val == severity)
			return (prioritynames[i].name);
	}
	return (NULL);
}

/*
 * Kludge to deal with %m format when not using syslog
 */
void
alog_expand(const char *fmt, int errnum, char *buf, size_t bufsize)
{
	const char *errstr;
	const char *s;
	int errlen;
	int i;

	/* Find "%m" which is usually at the end */
	for (s = fmt + strlen(fmt) - 2;
	    s >= fmt && !(s[0] == '%' && s[1] == 'm');
	    s--);

	/* Check if we should bother doing anything */
	if (s < fmt || (i = s - fmt) > bufsize - 2) {
		strncpy(buf, fmt, bufsize);
		return;
	}

	/* Convert "%m" to error string */
	errstr = strerror(errnum);
	errlen = strlen(errstr);
	strncpy(buf, fmt, i + 1);
	strncpy(buf + i, errstr, bufsize - i);
	strncpy(buf + i + errlen, fmt + i + 2, bufsize - i - errlen);
}

static int
asprintf(char **ret, const char *format, ...)
{
        va_list args;
        int rtn;

        va_start(args, format);
        rtn = vasprintf(ret, format, args);
        va_end(args);
        return (rtn);
}

static int
vasprintf(char **ret, const char *format, va_list ap)
{
        size_t buflen = 256;
        char *buf;
        int slen;

        /* Initialize return value in case of failure */
        *ret = NULL;

try_again:
        /* Allocate buffer */
        if ((buf = malloc(buflen)) == NULL) {
                errno = ENOMEM;
                return (-1);
        }

        /* Format string */
        slen = vsnprintf(buf, buflen, format, ap);

        /* If buffer was big enough, we're done */
        if (slen < buflen) {
                *ret = buf;
                return (slen);
        }

        /* Increase buffer size and try again */
        free(buf);
        buflen = slen + 1;
        goto try_again;
}

