/*
 * Copyright (c) 2003-2012
 * Distributed Systems Software.  All rights reserved.
 * See the file LICENSE for redistribution information.
 */

/*
 * Event logging
 * This library provides a mechanism by which applications can select, at
 * runtime, which of their tracing and debugging statements are to produce
 * output.  An "event" is simply any kind of status information that an
 * application would like to save for tracing, debugging, or security reasons.
 * Each logging event is tagged with descriptive information so that
 * the application can select some kinds of events for output and
 * discard others.
 *
 * The goal is to have an API that provides a convenient and flexible way
 * to select, at run time:
 *   1) the kinds of events that will be logged;
 *   2) the level of detail that will be logged for those events and
 *   3) where log information will be written.
 * To accomplish the first goal, logging can be enabled on a "module",
 * filename, function name, or program name basis.  For the second goal,
 * a set of priority levels, much like what Apache and syslog(3) use for the
 * same purpose, are used.  For the third, a set of output streams can be
 * specified.
 *
 * Logging is controlled through a logging descriptor.  After a descriptor is
 * initialized and configured, it can be set to be the "current logging
 * descriptor".  The need to have a current logging
 * descriptor is really a kludge.  The macros that initiate a logging event
 * place information in the current logging descriptor (a global variable)
 * so that it's available
 * to the log_exec() call that immediately follows.  This is obviously
 * not thread safe; a mutex around the two calls will solve the problem and
 * at the same time serialize concurrent logging event calls.
 * At present, these macros are partially dependent on having __FILE__,
 * __FUNCTION__, and __LINE__ macros available at compile time so that this
 * information
 * is available without any special preprocessing or programmer involvement.
 * Every file that uses a logging event macro must define a variable
 * like so:  static const char *log_module_name = "foo";
 * where "foo" is an arbitrary label that is intended to label the
 * functionality provided by the file; files that work together to provide
 * closely related functionality will bear the same log_module_name.
 *
 * Every logging event is tagged with a level (e.g., LOG_ALERT_LEVEL), optional
 * flags that are ORed with the level, and the source of the event (i.e.,
 * where the event is occurring: the module, filename, function name,
 * line number).  The macros automatically incorporate the source of the
 * event.
 *
 * A logging event statement generally consists of the level (with
 * optional flags), followed by a printf(3) format and a variable length
 * argument list.  Note that the arguments must appear within an extra pair
 * of parentheses, like so:
 *  log_msg((LOG_ERROR_LEVEL, "A %s error has occurred.", "major"));
 *
 * The current logging descriptor includes a level and optional flags.
 * If no filters are configured, only logging events having a level of at
 * least that of the descriptor's (and meeting any flag requirements) will be
 * emitted.
 *
 * In addition, a list of filters can be constructed to turn off various
 * kinds of log messages.  Each filter is compared
 * with the source of the event; the first matching filter is used.
 * For example, a filter that applies to the module name "foo"
 * will be processed for all logging messages generated by that module.
 * If the logging event meets the requirements of the matching filter,
 * the log message will be emitted, otherwise it will be discarded.
 *
 * Each filter has a level associated with it.  The level can be modified
 * by one or more flags (e.g., LOG_SENSITIVE_FLAG).
 * As a special case, a filter can have a level of LOG_USE_DEFAULT_LEVEL, which
 * means that the filter's level is specified by a default level within
 * the current logging descriptor.
 * If the level of the event is lower (more verbose) than the filter's level,
 * the message will be discarded.
 *
 * Logging can be totally enabled and disabled; when disabled, there is
 * virtually no overhead.  Log messages can be customized through a user
 * callback function.  There's also a mechanism for helping to generate
 * program traces based on the logging events.
 */

#ifndef lint
static const char copyright[] =
"Copyright (c) 2003-2012\n\
Distributed Systems Software.  All rights reserved.";
static const char revid[] =
  "$Id: loglib.c 2594 2012-10-19 17:28:49Z brachman $";
#endif

#include <syslog.h>

#include "log.h"

Log_desc *log_current_logd = NULL;

typedef struct Level_map {
  char *name;
  char *alt_names;
  Log_level level;
} Level_map;

/* Indexed by Log_level */
static Level_map log_levels[] = {
  { "trace",    "tracing",          LOG_TRACE_LEVEL },
  { "debug",    "debugging",        LOG_DEBUG_LEVEL },
  { "info",     NULL,               LOG_INFO_LEVEL },
  { "notice",   "notices",          LOG_NOTICE_LEVEL },
  { "warn",     "warning,warnings", LOG_WARN_LEVEL },
  { "error",    "errors",           LOG_ERROR_LEVEL },
  { "critical", "crit",             LOG_CRITICAL_LEVEL },
  { "alert",    "alerts",           LOG_ALERT_LEVEL },
  { "emerg",    "emergency",        LOG_EMERG_LEVEL },
  { "none",     NULL,               LOG_NONE_LEVEL } ,
  { NULL,       NULL,               LOG_INVALID_LEVEL }
};

/* Indexed by Log_source_type */
char *log_source_type_name[] = {
  "program", "module", "filename", "function", "any", NULL
};

Log_source_flag_map log_source_flag_map[] = {
  { "exact",     LOG_MATCH_EXACT },
  { "regex",     LOG_MATCH_REGEX },
  { "icase",     LOG_MATCH_ICASE },
  { "sensitive", LOG_MATCH_SENSITIVE },
  { "user",      LOG_MATCH_USER },
  { "audit",     LOG_MATCH_AUDIT },
  { "normal",    LOG_MATCH_NORMAL },
  { NULL,        (Log_source_flag) 0 }
};

static const char *log_module_name = "loglib";

static inline Log_desc *
get_logd(Log_desc *logd)
{
  Log_desc *ld;

  if (logd == NULL) {
	if (log_current_logd == NULL)
	  ld = NULL;
	else
	  ld = log_current_logd;
  }
  else
	ld = logd;

  return(ld);
}

Log_level
log_lookup_level(char *level_name)
{
  int i;

  for (i = 0; log_levels[i].name != NULL; i++) {
	if (strcaseeq(level_name, log_levels[i].name))
	  return(log_levels[i].level);
	if (log_levels[i].alt_names != NULL) {
	  int j;
	  Dsvec *dsv;

	  if ((dsv = strsplit(log_levels[i].alt_names, ",", 0)) != NULL) {
		for (j = 0; j < dsvec_len(dsv); j++) {
		  if (streq(dsvec_ptr(dsv, j, char *), level_name))
			return(log_levels[i].level);
		}
	  }
	}
  }

  return(LOG_INVALID_LEVEL);
}

static char *
log_level_to_name(Log_level level)
{
  int i;

  for (i = 0; log_levels[i].name != NULL; i++) {
	if (log_levels[i].level == level)
	  return(log_levels[i].name);
  }

  return(NULL);
}

int
log_is_valid_level(Log_level level)
{
  int lev;

  if (level == LOG_NONE_LEVEL || level == LOG_FORCE_LEVEL)
	return(1);

  lev = level & LOG_LEVEL_MASK;
  if (lev < LOG_LOWEST_LEVEL || lev > LOG_HIGHEST_LEVEL)
	return(0);

  return(1);
}

/*
 * If TO_SYSLOG is non-zero, or if FP is NULL and filename is "syslog",
 * force these log messages to go to syslog(3).
 * Otherwise, if FP is NULL, write log messages to stderr and if it is not
 * NULL write log messages to stream FP.
 * The FILENAME is primarily for debugging purposes.
 */
static Log_output *
log_output_init(FILE *fp, int to_syslog, char *filename)
{
  Log_output *new_out;
  static int did_openlog = 0;

  new_out = ALLOC(Log_output);
  if (to_syslog
	  || (fp == NULL && filename != NULL && streq(filename, "syslog"))) {
	new_out->fp = NULL;
	new_out->to_syslog = 1;
	new_out->filename = "syslog";
	if (!did_openlog) {
	  openlog("dacs", 0, LOG_USER);
	  did_openlog = 1;
	}
  }
  else if (fp != NULL) {
	new_out->fp = fp;
	new_out->to_syslog = 0;
	if (fp == stderr)
	  new_out->filename = "/dev/stderr";
	else if (fp == stdout)
	  new_out->filename = "/dev/stdout";
	else if (filename != NULL)
	  new_out->filename = strdup(filename);
	else
	  new_out->filename = "";
  }
  else {
	new_out->fp = stderr;
	new_out->to_syslog = 0;
	new_out->filename = "/dev/stderr";
  }

  new_out->next = NULL;

  return(new_out);
}

/*
 * Identical logging output may be written to multiple streams.
 * Add output target NEW to the list for descriptor LD.
 * If NEW is NULL, the target is stderr (convenient when debugging);
 * XXX This facility could be extended by adding an API to pipe the
 * stream to a process.
 * XXX This facility could be extended by connecting it to the DACS store
 * mechanism, which would include a way to pipe the stream to a process.
 */
int
log_add_output(Log_desc *ld, Log_output *new_out)
{
  Log_desc *logd;
  Log_output **out;

  if ((logd = get_logd(ld)) == NULL)
	return(-1);

  new_out->next = NULL;

  if (logd->output != NULL) {
	Log_output *ptr;

	for (ptr = logd->output; ptr->next != NULL; ptr = ptr->next)
	  ;
	out = &ptr->next;
  }
  else
	out = &logd->output;
  
  *out = new_out;

  return(0);
}

/*
 * Create a new log descriptor and initialize it.
 * Note that it must be set to the current log descriptor by log_set_desc()
 * and enabled before it comes into effect.
 */
Log_desc *
log_init(FILE *fp, int to_syslog, char *filename, char *program,
		 Log_level default_level, char *default_prefix)
{
  Log_desc *logd;
  Log_output *out;

  logd = ALLOC(Log_desc);
  logd->output = NULL;
  logd->event.level = LOG_INVALID_LEVEL;
  logd->event.program = strdup(program);
  logd->event.module = NULL;
  logd->event.filename = NULL;
  logd->event.funcname = NULL;
  logd->event.msg = NULL;
  logd->event.err = 0;
  logd->level = default_level;
  logd->flags = 0;
  logd->filters = NULL;
  logd->user_prefix = NULL;
  logd->user_data = NULL;
  logd->default_prefix = default_prefix;
  logd->trace_callback = NULL;
  logd->trace_data = NULL;
  logd->state = LOG_DISABLED;

  out = log_output_init(fp, to_syslog, filename);
  log_add_output(logd, out);

  return(logd);
}

/*
 * Tidy up and terminate all logging associated with LOGF (or the current
 * descriptor if LOGF is NULL).
 * XXX If we link together all Log_desc, then we can close all of them.
 */
int
log_end(Log_desc *ld)
{
  Log_desc *logd;
  Log_output *out;

  
  if ((logd = get_logd(ld)) == NULL)
	return(0);

  for (out = logd->output; out != NULL; out = out->next) {
	if (out->fp != NULL && out->fp != stderr && out->fp != stdout)
	  fclose(out->fp);
  }

  free(logd);

  return(0);
}

/*
 * Because there's no good and portable way for a log request to provide
 * this information, it is passed separately and stored in the current
 * (global) log descriptor.
 * Each logging request from the user will call this first.
 */
void
log_set_args(const char *module, char *filename, const char *funcname,
			 int line, int err, char *msg)
{

  if (log_current_logd == NULL)
	return;

  if (module != NULL)
	log_current_logd->event.module = (char *) module;
  else
	log_current_logd->event.module = NULL;
  if (filename != NULL)
	log_current_logd->event.filename = filename;
  else
	log_current_logd->event.filename = NULL;
  if (funcname != NULL)
	log_current_logd->event.funcname = (char *) funcname;
  else
	log_current_logd->event.funcname = NULL;
  log_current_logd->event.line = line;
  log_current_logd->event.err = err;
  log_current_logd->event.msg = msg;
}

/*
 * Set the current log descriptor and its state.
 * Return the log descriptor previously in effect (or NULL).
 */
Log_desc *
log_set_desc(Log_desc *logd, Log_state state)
{
  Log_desc *old_logd;

  old_logd = log_current_logd;
  if (logd != NULL)
	log_current_logd = logd;

  log_current_logd->state = state;

  return(old_logd);
}

/*
 * Set the logging level for LOGF (or the current descriptor if LOGF is NULL),
 * replacing the level initially set by log_init() or a previous call to
 * this function.
 * LEVEL is compared against an event's level if:
 *   o no filters have been specified OR
 *   o a source's level is LOG_USE_DEFAULT_LEVEL
 * otherwise only the source's level is important.
 *
 * LOG_ALL_LEVEL means that all logging levels should be recognized; it's
 * equivalent to setting the lowest logging level.  LOG_NONE means that no
 * logging level
 * should be recognized and is equivalent to setting the level beyond the
 * highest logging level argument used by a logging event.
 * Return the previous level in effect, or LOG_INVALID_LEVEL if an error
 * occurs.
 */
Log_level
log_set_level(Log_desc *logd, Log_level level)
{
  Log_desc *ld;
  Log_level old_level;

  if ((ld = get_logd(logd)) == NULL)
	return(LOG_INVALID_LEVEL);

  if (level != LOG_ALL_LEVEL && level != LOG_NONE_LEVEL
	  && (level < LOG_LOWEST_LEVEL || level > LOG_HIGHEST_LEVEL))
	return(LOG_INVALID_LEVEL);

  old_level = ld->level;
  ld->level = level;
  return(old_level);
}

unsigned int
log_get_flags(Log_desc *logd)
{
  Log_desc *ld;

  if ((ld = get_logd(logd)) == NULL)
	return(0);

  return(ld->flags);
}

/*
 * Set default flags, returning the previous ones.
 */
unsigned int
log_set_flags(Log_desc *logd, unsigned int flags)
{
  unsigned int old_flags;
  Log_desc *ld;

  if ((ld = get_logd(logd)) == NULL)
	return(0);

  old_flags = ld->flags;
  ld->flags = flags;

  return(old_flags);
}

/*
 * The user can configure a callback function with private data that will
 * be called when a log message is being produced.  If it returns non-NULL,
 * the string will prefix the rest of the log message.
 */
void
log_set_user_callback(Log_desc *logd,
					  char *(user_prefix)(Log_desc *, Log_level level, void *),
					  void *user_data)
{
  Log_desc *ld;

  if ((ld = get_logd(logd)) == NULL)
	return;

  ld->user_prefix = user_prefix;
  ld->user_data = user_data;
}

/*
 * The user can configure a callback function with private data that will
 * be called when a log message is being produced.  This is intended to be
 * used to produce a trace of program execution.  If it returns -1,
 * the callback will no longer be called.
 */
void
log_set_trace_callback(Log_desc *logd,
					   int (trace_callback)(Log_event *, void *),
					   void *trace_data)
{
  Log_desc *ld;

  if ((ld = get_logd(logd)) == NULL)
	return;

  ld->trace_callback = trace_callback;
  ld->trace_data = trace_data;
}

/*
 * Format a log message according to FMT:
 * %a  : application name
 * %c  : per-process message count
 * %%  : a percent character
 * %F  : event flags
 * %fn : federation name
 * %fd : federation domain
 * %j  : jurisdiction name
 * %l  : log message level
 * %p  : process id
 * %sp : source program name
 * %sm : source module name
 * %sf : source filename
 * %sF : source function name
 * %sl : source line number
 * %t  : current time and date
 * If % is followed by any other character, that character is printed.
 * Other characters in FMT are interpolated verbatim.
 * If a requested value is not available, nothing is interpolated.
 * The resulting string is returned.
 */
char *
log_format(Log_desc *logd, Log_level level, const char *fmt)
{
  char *p;
  const char *f;
  Ds ds;
  static int count = 1;

  if (fmt == NULL)
	return(NULL);

  ds_init(&ds);
  f = fmt;

  while (*f != '\0') {
    if (*f == '%') {
      f++;

      if (*f == '%')
        ds_asprintf(&ds, "%%");
	  else if (*f == 'a')
		ds_asprintf(&ds, "%s", get_dacs_app_name());
	  else if (*f == 'c')
		ds_asprintf(&ds, "%d", count++);
	  else if (*f == 'F') {
		int is_audit_event, is_sensitive_event, is_user_event;

		is_user_event = log_is_user_event(level);
		is_sensitive_event = log_is_sensitive_event(level);
		is_audit_event = log_is_audit_event(level);

		if (is_user_event || is_sensitive_event || is_audit_event) {
		  if (is_user_event)
			ds_asprintf(&ds, "U");
		  if (is_sensitive_event)
			ds_asprintf(&ds, "S");
		  if (is_audit_event)
			ds_asprintf(&ds, "A");
		}
		else
		  ds_asprintf(&ds, "-");
	  }
	  else if (*f == 'f') {
		f++;
		if (*f == 'n') {
		  if ((p = conf_val(CONF_FEDERATION_NAME)) != NULL)
			ds_asprintf(&ds, "%s", p);
		}
		else if (*f == 'd') {
		  if ((p = conf_val(CONF_FEDERATION_DOMAIN)) != NULL)
			ds_asprintf(&ds, "%s", p);
		}
	  }
	  else if (*f == 'j') {
		if ((p = conf_val(CONF_JURISDICTION_NAME)) != NULL)
		  ds_asprintf(&ds, "%s", p);
	  }
	  else if (*f == 'l')
		ds_asprintf(&ds, "%s", log_level_to_name(log_get_level(level)));
      else if (*f == 'p')
		ds_asprintf(&ds, "%d", getpid());
	  else if (*f == 's') {
		f++;
		if (*f == 'p') {
		  if ((p = logd->event.program) != NULL)
			ds_asprintf(&ds, "%s", p);
		}
		else if (*f == 'm') {
		  if ((p = logd->event.module) != NULL)
			ds_asprintf(&ds, "%s", p);
		}
		else if (*f == 'f') {
		  if ((p = logd->event.filename) != NULL)
			ds_asprintf(&ds, "%s", p);
		}
		else if (*f == 'F') {
		  if ((p = logd->event.funcname) != NULL)
			ds_asprintf(&ds, "%s", p);
		}
		else if (*f == 'l')
		  ds_asprintf(&ds, "%d", logd->event.line);
	  }
      else if (*f == 't') {
		char *d;
		time_t now;
		struct tm *tm;

		now = time(NULL);
		tm = localtime(&now);
		d = make_local_date_string(tm, 0);
		ds_asprintf(&ds, "%s", d);
	  }
	  else		
		ds_asprintf(&ds, "%c", *f);
    }
    else
      ds_asprintf(&ds, "%c", *f);
    f++;
  }

  return(ds_buf(&ds));
}

/*
 * Parse a filter specification string into its components.
 * Syntax is:
 *   <type> <flag>[,<flag>]* <level> <name> [[+|-]file]
 *   any <flag>[,<flag>]* <level> [[+|-]file]
 * Whitespace separates the components.
 * Return NULL on error, otherwise a structure containing the broken
 * out components.
 */
Log_filter_parse *
log_parse_filter(char *str)
{
  int i, j, n;
  char **argv, *p;
  Ds ds;
  Log_filter_parse *lfp;

  if ((n = mkargv(str, NULL, &argv)) < 3 || n > 5)
	return(NULL);

  lfp = ALLOC(Log_filter_parse);

  ds_init(&ds);
  for (i = 0; log_source_type_name[i] != NULL; i++) {
	if (strcaseeq(log_source_type_name[i], argv[0])) {
	  lfp->type = (Log_source_type) i;
	  ds_asprintf(&ds, "type=\"%s\" ", log_source_type_name[i]);
	  break;
	}
  }
  if (log_source_type_name[i] == NULL)
	return(NULL);

  if (lfp->type == LOG_ANY) {
	if (n == 5)
	  return(NULL);
  }
  else {
	if (n == 3)
	  return(NULL);
  }

  lfp->flags = LOG_MATCH_EXACT;		/* Depends on LOG_MATCH_EXACT being zero */
  ds_asprintf(&ds, "flags=\"");
  if (argv[1][0] != '\0') {
	char **fargv;
	Mkargv conf = { 0, 0, ",", NULL, NULL };

	if ((n = mkargv(argv[1], &conf, &fargv)) < 1)
	  return(NULL);

	for (i = 0; i < n; i++) {
	  for (j = 0; log_source_flag_map[j].name != NULL; j++) {
		if (strcaseeq(log_source_flag_map[j].name, fargv[i])) {
		  lfp->flags = (Log_source_flag)
			(lfp->flags | log_source_flag_map[j].flag);
		  ds_asprintf(&ds, "%s%s", i != 0 ? "," : "",
					  log_source_flag_map[j].name);
		  break;
		}
	  }
	  if (log_source_flag_map[j].name == NULL)
		return(NULL);
	}
  }
  ds_asprintf(&ds, "\" ");

  if ((lfp->level = log_lookup_level(argv[2])) == LOG_INVALID_LEVEL)
	return(NULL);
  ds_asprintf(&ds, "level=\"%s\"", log_level_to_name(lfp->level));

  i = 3;
  if (lfp->type != LOG_ANY) {
	lfp->source = strdup(argv[3]);
	ds_asprintf(&ds, " source=\"%s\"", argv[3]);
	i++;
  }
  else
	lfp->source = NULL;

  lfp->file = NULL;
  lfp->output_mode = LOG_OUTPUT_DEFAULT_MODE;
  if ((p = argv[i]) != NULL) {
	if (*p == '+') {
	  lfp->output_mode = LOG_OUTPUT_ADD_MODE;
	  p++;
	}
	else if (*p == '-') {
	  lfp->output_mode = LOG_OUTPUT_MINUS_MODE;
	  p++;
	}
	else if (*p == '\\')
	  p++;
	lfp->file = strdup(p);
	ds_asprintf(&ds, " file=\"%s\"", argv[i]);
  }

  /*
   * This log message is troublesome because it is emitted so early; it's
   * hard to adjust the logging level to what the application really wants
   * before this appears.
   */
#ifdef NOTDEF
  log_msg((LOG_TRACE_LEVEL, "Parsed filter spec: %s", ds_buf(&ds)));
#endif

  lfp->str = strdup(str);

  return(lfp);
}

/*
 * Restrict logging to messages associated with the source TYPE, of at
 * least LEVEL, and only those having a source that matches FILTER.
 * FLAGS specifies how the matching is to be done.
 * Return -1 on error, otherwise a unique identifier for this filter.
 * XXX Add an API to modify/remove filters using the identifier.
 */
int
log_set_filter(Log_desc *logd, Log_filter_parse *lfp)
{
  int st;
  Log_desc *ld;
  Log_filter *lf, *lfx;
  static int counter = 0;

  if (lfp->type != LOG_PROGRAM && lfp->type != LOG_MODULE
	  && lfp->type != LOG_FILENAME && lfp->type != LOG_FUNCNAME
	  && lfp->type != LOG_ANY)
	return(-1);
  if (lfp->source == NULL && lfp->type != LOG_ANY)
	return(-1);

  if ((ld = get_logd(logd)) == NULL)
	return(-1);

  lf = ALLOC(Log_filter);
  lf->type = lfp->type;
  if (lfp->level == LOG_GLOBAL_LEVEL)
	lf->level = logd->level;
  else
	lf->level = lfp->level;

  if (lfp->type == LOG_ANY)
	lf->str = NULL;
  else if (lfp->flags & LOG_MATCH_REGEX) {
	int rflags;

	lf->str = NULL;
	rflags = REG_EXTENDED;
	if (lfp->flags & LOG_MATCH_ICASE)
	  rflags |= REG_ICASE;

	if ((st = regcomp(&lf->regex, lfp->source, rflags)) != 0) {
	  char errbuf[128];

	  regerror(st, &lf->regex, errbuf, sizeof(errbuf));
	  /* Be careful not to recurse here. */
	  fprintf(stderr, "log_set_filter: regcomp error: %s\n", errbuf);
	  return(-1);
	}
  }
  else
	lf->str = strdup(lfp->source);
  lf->flags = lfp->flags;

  if (lfp->file != NULL) {
	char *errmsg, *path;
	FILE *logfp;

	if (get_logging_stream(lfp->file, &logfp, &path, &errmsg) == -1) {
	  if (errmsg != NULL)
		fprintf(stderr, "log_set_filter: %s\n", errmsg);
	  return(-1);
	}
	lf->output = log_output_init(logfp, 0, lfp->file);
  }
  else
	lf->output = NULL;
  lf->output_mode = lfp->output_mode;

  lf->next = NULL;

  if (ld->filters == NULL)
	ld->filters = lf;
  else {
	for (lfx = ld->filters; lfx->next != NULL; lfx = lfx->next)
	  ;
	lfx->next = lf;
  }

  lf->id = ++counter;

  return(lf->id);
}

int
log_add_filter(Log_desc *logd, char *str)
{
  int id;
  Log_desc *ld;
  Log_filter_parse *lfp;

  if ((ld = get_logd(logd)) == NULL)
	return(0);

  if ((lfp = log_parse_filter(str)) == NULL)
	return(-1);

  if ((id = log_set_filter(logd, lfp)) == -1)
	return(-1);

  /*
   * This log message is troublesome because it is emitted so early; it's
   * hard to adjust the logging level to what the application really wants
   * before this appears.
   */
#ifdef NOTDEF
  log_msg((LOG_TRACE_LEVEL, "Added filter: %s", str));
#endif
  return(id);
}

static int
lf_selects(Log_level level, Log_level flags, Log_event *event)
{
  int is_audit_event, is_normal_event, is_sensitive_event, is_user_event;
  Log_level event_lev;

  /*
   * If the event's level is less than the default level (or the filter's
   * level), ignore the logging request.
   */
  event_lev = log_get_level(event->level);
  if (event_lev < log_get_level(level))
	return(0);

  /* Get the message's attribute flags. */
  is_audit_event = log_is_audit_event(event->level);
  is_normal_event = log_is_normal_event(event->level);
  is_sensitive_event = log_is_sensitive_event(event->level);
  is_user_event = log_is_user_event(event->level);

  /*
   * Reject a message that does not meet the given criteria.
   * If the message survives this filter, it will be logged.
   * If the filter specifies the 'audit' modifier, then only 'audit'
   * tagged events can be selected unless 'normal' is also specified.
   */
  if (!is_audit_event && log_selects_audit(flags)
	  && !log_selects_normal(flags))
	return(0);
  if (is_sensitive_event && !log_selects_sensitive(flags))
	return(0);
  if (is_user_event && !log_selects_user(flags))
	return(0);
  if (is_normal_event && !log_selects_normal(flags))
	return(0);

  return(1);
}

/*
 * Decide if a log request at the given level and from the specified sources
 * should be emitted.
 * Return 1 if so, 0 otherwise, and -1 on error.
 */
static int
log_filter(Log_desc *logd, Log_filter **lf_match)
{
  int st;
  char *estr;
  Log_filter *lf;
  Log_level event_lev;

  if (lf_match != NULL)
	*lf_match = NULL;

  if (logd->event.level == LOG_FORCE_LEVEL)
	return(1);

  event_lev = log_get_level(logd->event.level);

  if (event_lev < LOG_LOWEST_LEVEL || event_lev > LOG_HIGHEST_LEVEL)
	return(0);

#ifdef NOTDEF
 {
   int is_audit_event, is_sensitive_event, is_user_event;

  is_audit_event = log_is_audit_event(logd->event.level);
  is_sensitive_event = log_is_sensitive_event(logd->event.level);
  is_user_event = log_is_user_event(logd->event.level);
  if (streq(get_dacs_app_name(),"dacs_authenticate"))
	if (is_audit_event) sleep(15);
 }
#endif

  if (logd->filters == NULL) {
	if (lf_selects(logd->level, logd->flags, &logd->event))
	  return(1);
	return(0);
  }

  /* Find the first applicable filter. */
  for (lf = logd->filters; lf != NULL; lf = lf->next) {
	/* Is this filter applicable? */
	switch (lf->type) {
	case LOG_PROGRAM:
	  estr = logd->event.program;
	  break;

	case LOG_MODULE:
	  estr = logd->event.module;
	  break;

	case LOG_FILENAME:
	  estr = logd->event.filename;
	  break;

	case LOG_FUNCNAME:
	  estr = logd->event.funcname;
	  break;

	case LOG_ANY:
	  estr = "";
	  break;

	default:
	  return(-1);
	}

	if (lf->type != LOG_ANY) {
	  if (estr == NULL)
		return(0);

	  st = REG_NOMATCH;
	  if (lf->str == NULL) {
		if ((st = regexec(&lf->regex, estr, 0, NULL, 0)) != 0) {
		  if (st != REG_NOMATCH) {
			char errbuf[128];

			regerror(st, &lf->regex, errbuf, sizeof(errbuf));
			/* Don't recurse here. */
			fprintf(stderr,
					"Regular expression execution error: %s\n", errbuf);
			return(-1);
		  }
		  continue;
		}
	  }
	  else {
		if ((lf->flags & LOG_MATCH_ICASE) && !strcaseeq(estr, lf->str))
		  continue;
		if ((lf->flags & LOG_MATCH_ICASE) == 0 && !streq(estr, lf->str))
		  continue;
	  }
	}

	/*
	 * A filter has matched so far...
	 */
	if (!lf_selects(log_get_level(lf->level) == LOG_USE_DEFAULT_LEVEL
				   ? logd->level : lf->level, lf->flags, &logd->event))
	  continue;
	  
	if (lf_match != NULL)
	  *lf_match = lf;
	return(1);
  }

  /* No source filter matched, so fall back on the default */
  if (!lf_selects(logd->level, logd->flags, &logd->event))
	return(0);
  return(1);
}

/*
 * This is where a log message is actually assembled and written.
 */
static void
log_output(Log_desc *logd, Log_output *out, Log_level level, char *prefix,
		   const char *fmt, va_list ap)
{
  Ds ds;

  ds_init(&ds);
  if (prefix != NULL)
	ds_asprintf(&ds, "%s ", prefix);

  /* Format the caller's actual message. */
  ds_vasprintf(&ds, fmt, ap);

  if (logd->event.msg != NULL) {
	ds_asprintf(&ds, ": %s", logd->event.msg);
	logd->event.msg = NULL;
  }

  if (logd->event.err) {
	ds_asprintf(&ds, ": %s", strerror(logd->event.err));
	logd->event.err = 0;
  }

  /* We now have the final log message - write it out. */

  if (out->to_syslog) {
	int priority;

	switch (log_get_level(level)) {
	case LOG_TRACE_LEVEL: priority = LOG_DEBUG; break;
	case LOG_DEBUG_LEVEL: priority = LOG_DEBUG; break;
	case LOG_INFO_LEVEL:  priority = LOG_INFO; break;
	case LOG_NOTICE_LEVEL: priority = LOG_NOTICE; break;
	case LOG_WARN_LEVEL:  priority = LOG_WARNING; break;
	case LOG_ERROR_LEVEL: priority = LOG_ERR; break;
	case LOG_CRITICAL_LEVEL: priority = LOG_CRIT; break;
	case LOG_ALERT_LEVEL: priority = LOG_ALERT; break;
	case LOG_EMERG_LEVEL: priority = LOG_EMERG; break;
	default:
	  if (log_get_level(level) < LOG_DEBUG_LEVEL)
		priority = LOG_DEBUG;
	  else
		priority = LOG_EMERG;
	  break;
	}
	syslog(priority, "%s", ds_buf(&ds));
  }
  else {
	fprintf(out->fp, "%s\n", ds_buf(&ds));
	fflush(out->fp);
  }
}

/*
 * Put a message to the logging stream, using printf(3) formatting arguments.
 * Since this function can be called as a result of an error, it must
 * not do anything that might lead to a recursive call to itself.
 */
static void
log_exec_internal(Log_desc *logd, Log_filter *lf, Log_level level,
				  const char *fmt, va_list ap)
{
  char *prefix;
  Log_output *out;
  va_list ap2;

  if (logd == NULL || logd->state != LOG_ENABLED)
	return;

  prefix = logd->default_prefix;
  if (logd->event.msg == NULL && logd->user_prefix != NULL)
	prefix = logd->user_prefix(logd, level, logd->user_data);

  if (lf != NULL && lf->output != NULL) {
	for (out = lf->output; out != NULL; out = out->next) {
	  va_copy(ap2, ap);
	  log_output(logd, out, level, prefix, fmt, ap2);
	  va_end(ap2);
	}
	if (lf->output_mode == LOG_OUTPUT_MINUS_MODE)
	  return;
  }

  for (out = logd->output; out != NULL; out = out->next) {
	va_copy(ap2, ap);
	log_output(logd, out, level, prefix, fmt, ap2);
	va_end(ap2);
  }
}

/*
 * Return 1 if a log message at LEVEL would be emitted, 0 otherwise.
 */
int
log_test(Log_level level)
{

  if (log_current_logd == NULL)
	return(1);

  log_current_logd->event.level = level;
  if (log_filter(log_current_logd, NULL) == 0)
	return(0);

  return(1);
}

int
log_vexec(Log_level level, const char *fmt, va_list ap)
{
  Log_filter *lf;

  if (log_current_logd == NULL) {
	vfprintf(stderr, fmt, ap);
	fprintf(stderr, "\n");

	return(1);
  }

  log_current_logd->event.level = level;
  if (log_filter(log_current_logd, &lf) == 0)
	return(0);

  if (log_current_logd->trace_callback != NULL) {
	if (log_current_logd->trace_callback(&log_current_logd->event,
										 log_current_logd->trace_data) == -1)
	  log_current_logd->trace_callback = NULL;
  }

  log_exec_internal(log_current_logd, lf, level, fmt, ap);

  return(1);
}

/*
 * This is what the user calls to log a message having the given LEVEL.
 */
int
log_exec(Log_level level, const char *fmt, ...)
{
  int st;
  va_list ap;

  va_start(ap, fmt);
  st = log_vexec(level, fmt, ap);
  va_end(ap);

  return(st);
}

char *
log_event_stamp(Log_desc *logd, Log_level level, void *arg)
{
  Ds ds;

  if (logd != NULL) {
	ds_init(&ds);
	ds_asprintf(&ds, "[%s:\"%s\", %s:%d:%s]",
				logd->event.program ? logd->event.program : "?",
				logd->event.module ? logd->event.module : "?",
				logd->event.filename ? logd->event.filename : "?",
				logd->event.line,
				logd->event.funcname ? logd->event.funcname : "?");
	return(ds_buf(&ds));
  }

  return("[Log message source unknown]");
}

/*
 * This callback will be disabled if it returns -1.
 */
static MAYBE_UNUSED int
trace_callback(Log_event *event, void *data)
{

  fprintf(stderr, "%s:\"%s\", %s:%d:%s\n",
		  event->program, event->module, event->filename, event->line,
		  event->funcname);

  return(0);
}

#ifdef PROG
/*
 * A simple program for testing and debugging.
 */
int
main(int argc, char **argv)
{
  Log_desc *logd;
  Log_filter_parse lfp;
  Log_output *out;
  FILE *fp;
  
  logd = log_init(NULL, 0, NULL, argv[0], LOG_DEFAULT_LEVEL, "[my prefix]");

  if ((fp = fopen("/tmp/logfile", "a+")) != NULL) {
	out = log_output_init(fp, 0, "/tmp/logfile");
	log_add_output(logd, out);
  }

  log_set_user_callback(logd, log_event_stamp, NULL);
  log_set_desc(logd, LOG_ENABLED);
  log_set_level(NULL, LOG_ALL_LEVEL);

  lfp.str = "";
  lfp.type = LOG_FILENAME;
  lfp.flags = LOG_MATCH_REGEX | LOG_MATCH_SENSITIVE;
  lfp.level = LOG_GLOBAL_LEVEL;
  lfp.source = "log.*";
  lfp.file = "/tmp/logfile";
  lfp.output_mode = LOG_OUTPUT_MINUS_MODE;
  log_set_filter(logd, &lfp);

  log_msg((LOG_INFO_LEVEL, "Hello %s %d", argv[0], argc));
  errno = ESTALE;
  log_err((LOG_INFO_LEVEL | LOG_SENSITIVE_FLAG,
		   "There really is not an error... %s %d", argv[0], argc));
  errno = 0;
  log_basic((LOG_INFO_LEVEL, "x=%d", argc), "bye");

  log_end(logd);

  exit(0);
}
#endif
