File:  [ELWIX - Embedded LightWeight unIX -] / embedaddon / libpdel / sys / alog.c
Revision 1.1.1.1 (vendor branch): download - view: text, annotated - select for diffs - revision graph
Tue Feb 21 23:25:53 2012 UTC (12 years, 3 months ago) by misho
Branches: libpdel, MAIN
CVS tags: v0_5_3, HEAD
libpdel


/*
 * Copyright (c) 2001-2002 Packet Design, LLC.
 * All rights reserved.
 * 
 * Subject to the following obligations and disclaimer of warranty,
 * use and redistribution of this software, in source or object code
 * forms, with or without modifications are expressly permitted by
 * Packet Design; provided, however, that:
 * 
 *    (i)  Any and all reproductions of the source or object code
 *         must include the copyright notice above and the following
 *         disclaimer of warranties; and
 *    (ii) No rights are granted, in any manner or form, to use
 *         Packet Design trademarks, including the mark "PACKET DESIGN"
 *         on advertising, endorsements, or otherwise except as such
 *         appears in the above copyright notice or in the software.
 * 
 * THIS SOFTWARE IS BEING PROVIDED BY PACKET DESIGN "AS IS", AND
 * TO THE MAXIMUM EXTENT PERMITTED BY LAW, PACKET DESIGN MAKES NO
 * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING
 * THIS SOFTWARE, INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED
 * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE,
 * OR NON-INFRINGEMENT.  PACKET DESIGN DOES NOT WARRANT, GUARANTEE,
 * OR MAKE ANY REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS
 * OF THE USE OF THIS SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY,
 * RELIABILITY OR OTHERWISE.  IN NO EVENT SHALL PACKET DESIGN BE
 * LIABLE FOR ANY DAMAGES RESULTING FROM OR ARISING OUT OF ANY USE
 * OF THIS SOFTWARE, INCLUDING WITHOUT LIMITATION, ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, PUNITIVE, OR CONSEQUENTIAL
 * DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, LOSS OF
 * USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
 * THE USE OF THIS SOFTWARE, EVEN IF PACKET DESIGN IS ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 *
 * Author: Archie Cobbs <archie@freebsd.org>
 */

#include <sys/types.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <sys/uio.h>
#include <sys/queue.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 "structs/structs.h"
#include "structs/type/array.h"
#include "structs/type/int.h"
#include "structs/type/ip4.h"
#include "structs/type/pointer.h"
#include "structs/type/string.h"
#include "structs/type/struct.h"
#include "structs/type/time.h"
#include "sys/alog.h"
#include "sys/logfile.h"
#include "util/typed_mem.h"

/* Memory allocation types */
#define MEM_TYPE		"alog"
#define MEM_TYPE_ENTRY		"alog_entry"
#define MEM_TYPE_HISTORY	"alog_history"
#define MEM_TYPE_CURCHAN	"alog_curchan"

/* 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 logfile		*logfile;	/* history logfile, 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 */
};

/*
 * 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,	},
	{ "authpriv",	LOG_AUTHPRIV,	},
	{ "cron", 	LOG_CRON,	},
	{ "daemon",	LOG_DAEMON,	},
	{ "ftp",	LOG_FTP,	},
	{ "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);

/*
 * 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)
		strlcpy(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 logfile */
	if (conf->histlen > 0) {
		if ((ch->logfile = logfile_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));
#ifndef __linux__
	sin.sin_len = sizeof(sin);
#endif
	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;
	logfile_close(&ch->logfile);			/* 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(MEM_TYPE, ch->last.msg);
	ch->last.msg = NULL;

	/* Close logfile */
	logfile_close(&ch->logfile);			/* 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(MEM_TYPE_CURCHAN, 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(MEM_TYPE_CURCHAN, 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(MEM_TYPE_CURCHAN, 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)
{
	const int errno_save = errno;
	struct alog_channel *const ch = &alog_channels[alog_get_channel()];
	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(MEM_TYPE, &msg, fmtbuf, args);
	if (msg == NULL) {
		fprintf(stderr, "%s: %s: %s\n",
		    __FUNCTION__, "vasprintf", 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(MEM_TYPE, 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(MEM_TYPE, 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(MEM_TYPE, 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);
		asctime_r(&tm, ascbuf);
		len = ASPRINTF(TYPED_MEM_TEMP, &netmsg, "<%d>%.15s %s",
		    LOG_MAKEPRI(ch->facility, sev), ascbuf + 4, msg);
		if (netmsg != NULL)
			write(ch->sock, netmsg, len);
		FREE(TYPED_MEM_TEMP, netmsg);
		break;
	    }
	}

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

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

		/* Add to history file */
		if (logfile_put(ch->logfile, ent, sizeof(*ent) + mlen) == -1) {
			fprintf(stderr, "%s: %s: %s\n",
			    __FUNCTION__, "logfile_put", strerror(errno));
		}
		FREE(MEM_TYPE, 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->logfile == NULL
	    || max_num == 0
	    || (num_ent = logfile_num_entries(ch->logfile)) == 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 = logfile_get(ch->logfile, 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(MEM_TYPE_HISTORY,
	    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(MEM_TYPE_ENTRY,
		    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 = logfile_get(ch->logfile, i, &len)) != NULL
		    && len >= sizeof(*ent) + 1
		    && MATCH(ent->sev, ent->when,
		      ent->msg, len - sizeof(*ent) - 1)) {
			if ((copy = MALLOC(MEM_TYPE_ENTRY, 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(MEM_TYPE, ch->last.msg);
	memset(&ch->last, 0, sizeof(ch->last));

	/* Zero out logfile */
	if (ch->logfile != NULL)
		logfile_trim(ch->logfile, 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) {
		strlcpy(buf, fmt, bufsize);
		return;
	}

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

/*********************************************************************
			STRUCTS TYPE DEFINITIONS
*********************************************************************/

/* Type for "path" and "name" strings in struct alog_config */
static const struct structs_type alog_path_type
	= STRUCTS_STRING_TYPE("alog_config.path", 1);
static const struct structs_type alog_name_type
	= STRUCTS_STRING_TYPE("alog_config.name", 1);

/* Fields list for struct alog_config */
static const struct structs_field alog_config_fields[] = {
	STRUCTS_STRUCT_FIELD(alog_config, path, &alog_path_type),
	STRUCTS_STRUCT_FIELD(alog_config, name, &alog_name_type),
	STRUCTS_STRUCT_FIELD(alog_config, facility, &alog_facility_type),
	STRUCTS_STRUCT_FIELD(alog_config, remote_server, &structs_type_ip4),
	STRUCTS_STRUCT_FIELD(alog_config, min_severity, &alog_severity_type),
	STRUCTS_STRUCT_FIELD(alog_config, histlen, &structs_type_int),
	STRUCTS_STRUCT_FIELD_END
};

/* Type for struct alog_config */
const struct structs_type alog_config_type
	= STRUCTS_STRUCT_TYPE(alog_config, &alog_config_fields);

/* Override method for alog_facility_type */
static structs_init_t		alog_facility_type_init;
static structs_binify_t		alog_facility_type_binify;

/* Type for syslog facility, which we store as a string. */
const struct structs_type alog_facility_type = {
	sizeof(char *),
	"alog",
	STRUCTS_TYPE_PRIMITIVE,
	alog_facility_type_init,
	structs_ascii_copy,
	structs_string_equal,
	structs_string_ascify,
	alog_facility_type_binify,
	structs_string_encode,
	structs_string_decode,
	structs_string_free,
	{ { (void *)"alog_config.facility" }, { (void *)1 } }
};

/*
 * Initializer for alog_facility_type
 */
static int
alog_facility_type_init(const struct structs_type *type, void *data)
{
	return (structs_string_binify(type, "daemon", data, NULL, 0));
}

/*
 * Binifier for alog_facility_type
 */
static int
alog_facility_type_binify(const struct structs_type *type,
	const char *ascii, void *data, char *ebuf, size_t emax)
{
	if (*ascii != '\0' && alog_facility(ascii) == -1) {
		strlcpy(ebuf, "invalid syslog facility", emax);
		errno = EINVAL;
		return (-1);
	}
	return (structs_string_binify(type, ascii, data, NULL, 0));
}

/* Override methods for alog_severity_type */
static structs_init_t		alog_severity_init;
static structs_ascify_t		alog_severity_ascify;
static structs_binify_t		alog_severity_binify;

/* Type for severity, which defaults to LOG_INFO. The input can be either
   a symbolic name or a numeric value. */
const struct structs_type alog_severity_type = {
	sizeof(int),
	"int",
	STRUCTS_TYPE_PRIMITIVE,
	alog_severity_init,
	structs_region_copy,
	structs_region_equal,
	alog_severity_ascify,
	alog_severity_binify,
	structs_region_encode_netorder,
	structs_region_decode_netorder,
	structs_nothing_free,
	{ { (void *)2 }, { (void *)1 } }
};

/*
 * Initializer for severity
 */
static int
alog_severity_init(const struct structs_type *type, void *data)
{
	*((int *)data) = LOG_INFO;
	return (0);
}

/*
 * Ascifier for severity
 */
static char *
alog_severity_ascify(const struct structs_type *type,
	const char *mtype, const void *data)
{
	const int sev = *((int *)data);
	const char *s;
	char buf[32];

	if ((s = alog_severity_name(sev)) == NULL) {
		snprintf(buf, sizeof(buf), "%d", sev);
		s = buf;
	}
	return (STRDUP(mtype, s));
}

/*
 * Binifier for severity
 */
static int
alog_severity_binify(const struct structs_type *type,
	const char *ascii, void *data, char *ebuf, size_t emax)
{
	int sev;

	if ((sev = alog_severity(ascii)) == -1
	    && structs_int_binify(type, ascii, data, NULL, 0) == -1) {
		strlcpy(ebuf, "invalid syslog severity", emax);
		return (-1);
	}
	*((int *)data) = sev;
	return (0);
}

/*
 * Ascifier for log entry message.
 */
static char *
alog_entry_msg_ascify(const struct structs_type *type,
	const char *mtype, const void *data)
{
	return (STRDUP(mtype, data));
}

/* Type for log entry message. This is a "read only" type. */
static const struct structs_type alog_entry_msg_type = {
	0,
	"alog_entry_msg_type",
	STRUCTS_TYPE_PRIMITIVE,
	structs_notsupp_init,
	structs_notsupp_copy,
	structs_notsupp_equal,
	alog_entry_msg_ascify,
	structs_notsupp_binify,
	structs_notsupp_encode,
	structs_notsupp_decode,
	structs_nothing_free,
};

/* Type for history list: an array of pointers to log entries */
static const struct structs_field alog_entry_fields[] = {
	STRUCTS_STRUCT_FIELD(alog_entry, when, &structs_type_time_abs),
	STRUCTS_STRUCT_FIELD(alog_entry, sev, &alog_severity_type),
	STRUCTS_STRUCT_FIELD(alog_entry, msg, &alog_entry_msg_type),
	STRUCTS_STRUCT_FIELD_END
};
static const struct structs_type alog_entry_type
	= STRUCTS_STRUCT_TYPE(alog_entry, &alog_entry_fields);
static const struct structs_type alog_history_ptr_type
	= STRUCTS_POINTER_TYPE(&alog_entry_type, MEM_TYPE_ENTRY);
const struct structs_type alog_history_type
	= STRUCTS_ARRAY_TYPE(&alog_history_ptr_type, MEM_TYPE_HISTORY, "entry");


FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>