/*
 * Copyright (c) 2003-2006 Sendmail, Inc. and its suppliers.
 *	All rights reserved.
 *
 * By using this file, you agree to the terms and conditions set
 * forth in the LICENSE file which can be found at the top level of
 * the sendmail distribution.
 */

#include "sm/generic.h"
SM_RCSID("@(#)$Id: log.c,v 1.41 2007/01/14 00:44:40 ca Exp $")
#include "sm/assert.h"
#include "sm/error.h"
#include "sm/time.h"
#include "sm/magic.h"
#include "sm/heap.h"
#include "sm/memops.h"
#include "sm/io.h"
#include "sm/log.h"
#include "sm/syslog.h"
#include "sm/time.h"
#include "sm/str.h"
#if MTA_USE_PTHREADS
#include "sm/pthread.h"
#endif

#define SM_LOG_MAGIC SM_MAGIC('S', 'L', 'O', 'G')

#define MAX_LOG_ENTRIES	10000

/* can syslog() deal with these lengths? */
#ifdef MTA_LOG_LEN
# define LOG_LEN MTA_LOG_LEN
#else
# define LOG_LEN 1022
#endif
#ifdef MTA_LOG_LEN_MAX
# define LOG_LEN_MAX MTA_LOG_LEN_MAX
#else
# define LOG_LEN_MAX 8192
#endif

#if LOG_LEN > LOG_LEN_MAX
 ERROR _LOG_LEN > _LOG_LEN_MAX: LOG_LEN > LOG_LEN_MAX
#endif

/* should be dynamically allocated later on */
#define SM_LOG_MAX_CATS	32
#define SM_LOG_MAX_MODS	32

struct sm_log_ctx_S
{
	sm_magic_T	 sm_magic;
	sm_logconfig_P	 lctx_cfg;
	uint		 lctx_dbg_level;
#if 0
	/* make some things optional: sev=, time stamp, ...?? */
	uint		 lctx_flags;
#endif
	int		 lctx_fd;
	sm_file_T	*lctx_fp;
	char		*lctx_filename;
	sm_str_P	 lctx_str;
	sm_logcategory_P lctx_cats[SM_LOG_MAX_CATS];
	uint		 lctx_cat_n;
	sm_logmodule_P	 lctx_mods[SM_LOG_MAX_MODS];
	uint		 lctx_mod_n;
#if SM_LOG_ROTATE
	/* doesn't work that simple: needs filename */
	time_T		 lctx_rotated;
	uint		 lctx_entries;
#endif
#if MTA_USE_PTHREADS
	pthread_mutex_t	 lctx_mutex;
#endif
};

#define SM_IS_LOGCTX(lctx) SM_REQUIRE_ISA((lctx), SM_LOG_MAGIC)

/* move to magic.h? */
#define SM_LOGCFG_MAGIC SM_MAGIC('S', 'L', 'C', 'F')

struct sm_logconfig_S
{
	sm_magic_T	 sm_magic;
};
#define SM_IS_LOGCFG(lcfg) SM_REQUIRE_ISA((lcfg), SM_LOGCFG_MAGIC)

#if SM_LOG_SEVERITY

/*
**  log sev=SEVERITY () works nice here, but other stuff like
**  syslog(), fprint(), don't do this...
**  change all sm_log_write() calls?
*/

struct l2t_S
{
	int	 l2t_code;
	char	*l2t_str;
};
typedef struct l2t_S	l2t_T, *l2t_P;

static l2t_T
sm_loglev2txt[] =
{
	{ SM_LOG_EMERG,		"EMERG"	},
	{ SM_LOG_FATAL,		"FATAL"	},
	{ SM_LOG_ALERT,		"ALERT"	},
	{ SM_LOG_CRIT,		"CRIT"	},
	{ SM_LOG_ERROR,		"ERROR"	},
	{ SM_LOG_ERR,		"ERROR"	},
	{ SM_LOG_WARNING,	"WARNING"	},
	{ SM_LOG_WARN,		"WARNING"	},
	{ SM_LOG_NOTICE,	"NOTICE"	},
	{ SM_LOG_INFO,		"INFO"	},
	{ SM_LOG_DEBUG,		"DEBUG"	},
	{ -1,			 NULL	}
};

static char *
sm_l2t(int loglevel)
{
	int i;

	i = 0;
	while (sm_loglev2txt[i].l2t_code != 0 || sm_loglev2txt[i].l2t_str != NULL)
	{
		if (sm_loglev2txt[i].l2t_code == loglevel)
			return sm_loglev2txt[i].l2t_str;
		++i;
	}
	return "Bogus_Log_Level";
}
#endif /* SM_LOG_SEVERITY */

/*
**  SM_LOG_CREATE -- Create a log context
**
**	Parameters:
**		mctx -- memory context to use [currently unused]
**		plctx -- (pointer to) log context (output)
**		plcfg -- (pointer to) log config (output)
**
**	Returns:
**		usual sm_error code; ENOMEM
*/

sm_ret_T
sm_log_create(sm_mem_P mctx, sm_log_ctx_P *plctx, sm_logconfig_P *plcfg)
{
	sm_ret_T ret;
	sm_log_ctx_P lctx;
	sm_logconfig_P lcfg;

	(void)mctx;
	SM_REQUIRE(plctx != NULL);

	lctx = (sm_log_ctx_P) sm_zalloc(sizeof(*lctx));
	if (NULL == lctx)
		return sm_error_temp(SM_EM_LOG, ENOMEM);
#if MTA_USE_PTHREADS
	ret = pthread_mutex_init(&lctx->lctx_mutex, SM_PTHREAD_MUTEXATTR);
	if (ret != 0) {
		/* ret = sm_error_perm(SM_EM_LOG, ret); */
		ret = sm_error_perm(SM_EM_LOG, ret);
		goto error;
	}
#endif /* MTA_USE_PTHREADS */
	lctx->lctx_str = sm_str_new(NULL, LOG_LEN, LOG_LEN_MAX);
	if (NULL == lctx->lctx_str) {
		ret = sm_error_temp(SM_EM_LOG, ENOMEM);
		goto error;
	}
	lctx->lctx_fd = INVALID_FD;
	lctx->sm_magic = SM_LOG_MAGIC;
	ret = sm_logconfig_create(lctx, &lcfg);
	if (sm_is_err(ret))
		goto error;
	lctx->lctx_cfg = lcfg;
	if (plcfg != NULL)
		*plcfg = lcfg;
	lctx->lctx_dbg_level = UINT_MAX;
	*plctx = lctx;
	return SM_SUCCESS;

  error:
	if (lctx != NULL) {
		SM_STR_FREE(lctx->lctx_str);
		lctx->sm_magic = SM_MAGIC_NULL;
		sm_free_size(lctx, sizeof(*lctx));
	}
	return ret;
}

/*
**  SM_LOG_DESTROY -- Destroy a log context
**
**	Parameters:
**		lctx -- log context
**
**	Returns:
**		SM_SUCCESS
*/

sm_ret_T
sm_log_destroy(sm_log_ctx_P lctx)
{
	if (NULL == lctx)
		return SM_SUCCESS;
	SM_IS_LOGCTX(lctx);
	if (lctx->lctx_fp != NULL)
		sm_io_flush(lctx->lctx_fp);
	sm_logconfig_destroy(lctx->lctx_cfg);
#if MTA_USE_PTHREADS
	(void) pthread_mutex_destroy(&lctx->lctx_mutex);
#endif
	SM_STR_FREE(lctx->lctx_str);
	lctx->sm_magic = SM_MAGIC_NULL;
	sm_free_size(lctx, sizeof(*lctx));
	return SM_SUCCESS;
}

/*
**  SM_LOGCONFIG_CREATE -- Create a log configuration
**
**	Parameters:
**		lctx -- log context
**		plcfg -- (pointer to) log config (output)
**
**	Returns:
**		usual sm_error code; ENOMEM
*/

sm_ret_T
sm_logconfig_create(sm_log_ctx_P lctx, sm_logconfig_P *plcfg)
{
	sm_logconfig_P lcfg;

	SM_REQUIRE(lctx != NULL);
	SM_REQUIRE(plcfg != NULL);

	lcfg = (sm_logconfig_P) sm_zalloc(sizeof(*lcfg));
	if (NULL == lcfg)
		return sm_error_temp(SM_EM_LOG, ENOMEM);
	lcfg->sm_magic = SM_LOGCFG_MAGIC;
	*plcfg = lcfg;
	return SM_SUCCESS;
}

/*
**  SM_LOGCONFIG_USE -- Use a log configuration
**
**	Parameters:
**		lctx -- log context
**		lcfg -- log config
**
**	Returns:
**		SM_SUCCESS
*/

sm_ret_T
sm_logconfig_use(sm_log_ctx_P lctx, sm_logconfig_P lcfg)
{
	SM_IS_LOGCTX(lctx);
	SM_IS_LOGCFG(lcfg);
	return SM_SUCCESS;
}

/*
**  SM_LOGCONFIG_GET -- Return current log configuration
**
**	Parameters:
**		lctx -- log context
**
**	Returns:
**		log config
*/

sm_logconfig_P
sm_logconfig_get(sm_log_ctx_P lctx)
{
	SM_IS_LOGCTX(lctx);
	return lctx->lctx_cfg;
}

/*
**  SM_LOGCONFIG_DESTROY -- Destroy a log configuration
**
**	Parameters:
**		lcfg -- log config
**
**	Returns:
**		SM_SUCCESS
*/

sm_ret_T
sm_logconfig_destroy(sm_logconfig_P lcfg)
{
	if (NULL == lcfg)
		return SM_SUCCESS;
	SM_IS_LOGCFG(lcfg);
	lcfg->sm_magic = SM_MAGIC_NULL;
	sm_free_size(lcfg, sizeof(*lcfg));
	return SM_SUCCESS;
}

sm_ret_T
sm_log_registercategories(sm_log_ctx_P lctx, sm_logcategory_T categories[])
{
	uint u;

	SM_IS_LOGCTX(lctx);
	for (u = 0; u < SM_ARRAY_SIZE(lctx->lctx_cats); u++) {
		if (NULL == lctx->lctx_cats[u]) {
			lctx->lctx_cats[u] = categories;
			++lctx->lctx_cat_n;
			return SM_SUCCESS;
		}
	}
	return sm_error_perm(SM_EM_LOG, SM_E_FULL);
}

sm_ret_T
sm_log_registermodules(sm_log_ctx_P lctx, sm_logmodule_T modules[])
{
	uint u;

	SM_IS_LOGCTX(lctx);
	for (u = 0; u < SM_ARRAY_SIZE(lctx->lctx_mods); u++) {
		if (NULL == lctx->lctx_mods[u]) {
			lctx->lctx_mods[u] = modules;
			++lctx->lctx_mod_n;
			return SM_SUCCESS;
		}
	}
	return sm_error_perm(SM_EM_LOG, SM_E_FULL);
}

/* ARGSUSED1 */
sm_ret_T
sm_log_createchannel(sm_logconfig_P lcfg, const char *name, uint type, int priority, uint level, const sm_logdestination_P destination, uint flags)
{
	SM_IS_LOGCFG(lcfg);

	return SM_SUCCESS;
}

/* ARGSUSED1 */
sm_ret_T
sm_log_usechannel(sm_logconfig_P lcfg, const char *name, const sm_logcategory_P category, const sm_logmodule_P module)
{
	SM_IS_LOGCFG(lcfg);

	return SM_SUCCESS;
}

/*
**  SM_LOG_SETFILE -- Set a logfile in a log context
**
**	Parameters:
**		lctx -- log context
**		file -- file to use
**
**	Returns:
**		SM_SUCCESS
*/

sm_ret_T
sm_log_setfile(sm_log_ctx_P lctx, sm_file_T *file)
{
	SM_IS_LOGCTX(lctx);

	lctx->lctx_fp = file;
	return SM_SUCCESS;
}

/*
**  SM_LOG_SETFD -- Set a log fd in a log context
**
**	Parameters:
**		lctx -- log context
**		fd -- fd to use
**
**	Returns:
**		SM_SUCCESS
*/

sm_ret_T
sm_log_setfd(sm_log_ctx_P lctx, int fd)
{
	SM_IS_LOGCTX(lctx);

	lctx->lctx_fd = fd;
	return SM_SUCCESS;
}

/*
**  SM_LOG_SETFP_FD -- Set a logfile and fd in a log context
**
**	Parameters:
**		lctx -- log context
**		fp -- file to use
**		fd -- fd to use
**
**	Returns:
**		SM_SUCCESS
*/

sm_ret_T
sm_log_setfp_fd(sm_log_ctx_P lctx, sm_file_T *fp, int fd)
{
	SM_IS_LOGCTX(lctx);

	lctx->lctx_fp = fp;
	lctx->lctx_fd = fd;
	return SM_SUCCESS;
}

/*
**  SM_LOG_SETFILENAME -- Set a logfile name in a log context
**
**	Parameters:
**		lctx -- log context
**		name -- file name to use (will NOT be copied!)
**
**	Returns:
**		SM_SUCCESS
*/

sm_ret_T
sm_log_setfilename(sm_log_ctx_P lctx, char *name)
{
	SM_IS_LOGCTX(lctx);

	lctx->lctx_filename = name;
	return SM_SUCCESS;
}

/*
**  SM_LOG_REOPEN -- Reopen a log file
**
**	Parameters:
**		lctx -- log context
**
**	Returns:
**		usual sm_error code.
*/

sm_ret_T
sm_log_reopen(sm_log_ctx_P lctx)
{
	sm_ret_T ret;
#if MTA_USE_PTHREADS
	int r;
#endif

	SM_IS_LOGCTX(lctx);

	ret = SM_SUCCESS;
#if MTA_USE_PTHREADS
	r = pthread_mutex_lock(&lctx->lctx_mutex);
	SM_LOCK_OK(r);
	if (r != 0) {
		/* ret = sm_error_perm(SM_EM_LOG, r); */
		ret = sm_error_perm(SM_EM_LOG, r);
		goto error;
	}
#endif /* MTA_USE_PTHREADS */
	if (lctx->lctx_filename != NULL && *lctx->lctx_filename != '\0') {
		sm_io_flush(lctx->lctx_fp);
		sm_io_close(lctx->lctx_fp, SM_IO_CF_NONE);
		lctx->lctx_fp = NULL;
		ret = sm_io_open(SmStStdio, lctx->lctx_filename, SM_IO_WRONLY,
			&lctx->lctx_fp, SM_IO_WHAT_END);
		if (sm_is_err(ret))
			goto errunl;
	}
	else if (NULL == lctx->lctx_fp || lctx->lctx_fd == INVALID_FD)
		/* nothing */;	/* XXX Really? */
	else {
		sm_io_flush(lctx->lctx_fp);
		ret = ftruncate(lctx->lctx_fd, (off_t) 0);
		if (ret < 0) {
			ret = sm_error_temp(SM_EM_LOG, errno);
			goto errunl;
		}
		ret = sm_io_seek(lctx->lctx_fp, 0L, SM_IO_SEEK_SET);
		if (sm_is_err(ret))
			goto errunl;
	}

#if SM_LOG_ROTATE
	lctx->lctx_entries = 0;
	lctx->lctx_rotated = time(NULLT);
#endif
#if MTA_USE_PTHREADS
	r = pthread_mutex_unlock(&lctx->lctx_mutex);
	SM_ASSERT(r == 0);
	if (r != 0 && sm_is_success(ret))
		ret = sm_error_perm(SM_EM_LOG, r);
#endif /* MTA_USE_PTHREADS */
	return ret;

  errunl:
#if MTA_USE_PTHREADS
	r = pthread_mutex_unlock(&lctx->lctx_mutex);
	SM_ASSERT(r == 0);
	if (r != 0 && sm_is_success(ret))
		ret = sm_error_perm(SM_EM_LOG, r);
  error:
#endif /* MTA_USE_PTHREADS */
	return ret;
}

/*
**  SM_LOG_TSTAMP -- return timestamp
**
**	Parameters:
**		none.
**
**	Returns:
**		pointer to timestamp.
**
**	Locking: must be provided by caller.
*/

static char *
sm_log_tstamp(void)
{
	static char str[32] = "[1900-00-00/00:00:00] ";
	static time_t lastt = 0;
	struct tm *tmp;
	time_t currt;
#if MTA_USE_PTHREADS
	struct tm tm;
#endif

#if MTA_USE_STATETHREADS
	currt = st_time();
#else
	currt = time(NULLT);
#endif
	if (currt == lastt || currt == (time_t) -1)
		return str;
#if MTA_USE_PTHREADS
	tmp = localtime_r(&currt, &tm);
#else
	tmp = localtime(&currt);
#endif

	sm_snprintf(str, sizeof(str), "[%d-%02d-%02d/%02d:%02d:%02d] ",
		1900 + tmp->tm_year,	/* HACK */
		tmp->tm_mon + 1,
		tmp->tm_mday,
		tmp->tm_hour, tmp->tm_min, tmp->tm_sec);
	lastt = currt;
	return str;
}

#if SM_LOG_ROTATE
/*
**  SM_LOG_ROTATE -- Rotate a log file
**
**	Parameters:
**		lctx -- log context
**
**	Returns:
**		SM_SUCCESS
*/

sm_ret_T
sm_log_rotate(sm_log_ctx_P lctx)
{
	SM_IS_LOGCTX(lctx);

	if (lctx->lctx_fp != NULL)
	{
	}
	lctx->lctx_entries = 0;
	lctx->lctx_rotated = time(NULLT);
	return SM_SUCCESS;
}
#endif /* SM_LOG_ROTATE */

/*
**  SM_LOG_WRITE -- Write a logfile entry
**
**	Parameters:
**		lctx -- log context
**		category -- category
**		module -- module
**		priority -- priority
**		level -- log level
**		format -- format
**		... -- arguments
**
**	Returns:
**		SM_SUCCESS except for (un)lock errors
*/

sm_ret_T
sm_log_write(sm_log_ctx_P lctx, sm_logcategory_P category, sm_logmodule_P module, int priority, uint level, const char *format, ...)
{
	va_list va;
#if MTA_USE_PTHREADS
	int r;
#endif
	sm_ret_T ret;

	if (lctx != NULL) {
		SM_IS_LOGCTX(lctx);
		if (!sm_log_wouldlog(lctx, category, module, level))
			return SM_SUCCESS;
	}

	/* Allow NULL context, necessary for emergency logging */
	if (NULL == lctx) {
		int r, save_errno;
		char *log;

		log = NULL;
		va_start(va, format);
		r = sm_vasprintf(&log, format, va);
		save_errno = errno;
		if (r != -1)
			syslog(priority, "%.*s", LOG_LEN_MAX, log);
		va_end(va);
		SM_FREE(log);
		return (r != -1) ? SM_SUCCESS
				: sm_error_perm(SM_EM_LOG, save_errno);
	}

	ret = SM_SUCCESS;
#if MTA_USE_PTHREADS
	r = pthread_mutex_lock(&lctx->lctx_mutex);
	SM_LOCK_OK(r);
	if (r != 0) {
		/* ret = sm_error_perm(SM_EM_LOG, r); */
		return sm_error_perm(SM_EM_LOG, r);
	}
#endif /* MTA_USE_PTHREADS */
	if (NULL == lctx->lctx_fp) {
		sm_str_clr(lctx->lctx_str);
#if SM_LOG_SEVERITY
		sm_str_scat(lctx->lctx_str, sm_l2t(priority));
#endif
		va_start(va, format);
		sm_strvprintf(lctx->lctx_str, format, va);
		va_end(va);
		syslog(priority, "%.*s", (int) sm_str_getlen(lctx->lctx_str),
			(const char *) sm_str_data(lctx->lctx_str));
	}
	else {
		sm_io_fputs(lctx->lctx_fp, (const uchar *) sm_log_tstamp());
#if SM_LOG_SEVERITY
		sm_io_fprintf(lctx->lctx_fp, "sev=%s, ", sm_l2t(priority));
#endif
		va_start(va, format);
		sm_io_vfprintf(lctx->lctx_fp, format, va);
		va_end(va);
		sm_putc(lctx->lctx_fp, '\n');
#if SM_LOG_ROTATE
		if (++lctx->lctx_entries > MAX_LOG_ENTRIES)
			sm_log_rotate(lctx);
#endif
	}
#if MTA_USE_PTHREADS
	r = pthread_mutex_unlock(&lctx->lctx_mutex);
	SM_ASSERT(r == 0);
	if (r != 0 && sm_is_success(ret))
		ret = sm_error_perm(SM_EM_LOG, r);
#endif /* MTA_USE_PTHREADS */
	return ret;
}

/*
**  SM_LOG_VWRITE -- Write a logfile entry
**
**	Parameters:
**		lctx -- log context
**		category -- category
**		module -- module
**		priority -- priority
**		format -- format
**		args -- arguments
**
**	Returns:
**		SM_SUCCESS except for (un)lock errors
*/

sm_ret_T
sm_log_vwrite(sm_log_ctx_P lctx, sm_logcategory_P category, sm_logmodule_P module, int priority, uint level, const char *format, va_list args)
{
#if MTA_USE_PTHREADS
	int r;
#endif
	sm_ret_T ret;

	/* Allow NULL context, necessary for emergence logging. */
	if (lctx != NULL) {
		SM_IS_LOGCTX(lctx);
		if (lctx->lctx_dbg_level < level)
			return SM_SUCCESS;
	}

	/* HACK: this doesn't really work, see begin of file */
	if (NULL == lctx) {
		int r, save_errno;
		char *log;

		log = NULL;
		r = sm_vasprintf(&log, format, args);
		save_errno = errno;
		if (r != -1)
			syslog(priority, "%.*s", LOG_LEN_MAX, log);
		SM_FREE(log);
		return (r != -1) ? SM_SUCCESS
				: sm_error_perm(SM_EM_LOG, save_errno);
	}

	ret = SM_SUCCESS;
#if MTA_USE_PTHREADS
	r = pthread_mutex_lock(&lctx->lctx_mutex);
	SM_LOCK_OK(r);
	if (r != 0) {
		/* ret = sm_error_perm(SM_EM_LOG, r); */
		return sm_error_perm(SM_EM_LOG, r);
	}
#endif /* MTA_USE_PTHREADS */
	if (NULL == lctx->lctx_fp) {
		sm_str_clr(lctx->lctx_str);
#if SM_LOG_SEVERITY
		sm_str_scat(lctx->lctx_str, sm_l2t(priority));
#endif
		sm_strvprintf(lctx->lctx_str, format, args);
		syslog(priority, "%.*s", (int) sm_str_getlen(lctx->lctx_str),
			(const char *) sm_str_data(lctx->lctx_str));
	}
	else {
		sm_io_fputs(lctx->lctx_fp, (const uchar *) sm_log_tstamp());
#if SM_LOG_SEVERITY
		sm_io_fprintf(lctx->lctx_fp, "sev=%s, ", sm_l2t(priority));
#endif
		sm_io_vfprintf(lctx->lctx_fp, format, args);
		sm_putc(lctx->lctx_fp, '\n');
#if SM_LOG_ROTATE
		if (++lctx->lctx_entries > MAX_LOG_ENTRIES)
			sm_log_rotate(lctx);
#endif
	}
#if MTA_USE_PTHREADS
	r = pthread_mutex_unlock(&lctx->lctx_mutex);
	SM_ASSERT(r == 0);
	if (r != 0 && sm_is_success(ret))
		ret = sm_error_perm(SM_EM_LOG, r);
#endif /* MTA_USE_PTHREADS */
	return ret;
}

#if 0
sm_ret_T
sm_log_write1(sm_log_ctx_P lctx, sm_logcategory_P category, sm_logmodule_P module, int priority, int level, const char *format, ...)
{
	SM_IS_LOGCTX(lctx);

	return SM_SUCCESS;
}

sm_ret_T
sm_log_vwrite1(sm_log_ctx_P lctx, sm_logcategory_P category, sm_logmodule_P module, int priority, int level, const char *format, va_list args)
{
	SM_IS_LOGCTX(lctx);

	return SM_SUCCESS;
}
#endif /* 0 */

sm_ret_T
sm_log_setdebuglevel(sm_log_ctx_P lctx, uint level)
{
	SM_IS_LOGCTX(lctx);

	lctx->lctx_dbg_level = level;
	return SM_SUCCESS;
}

uint
sm_log_getdebuglevel(sm_log_ctx_P lctx)
{
	SM_IS_LOGCTX(lctx);

	return lctx->lctx_dbg_level;
}

/*
**  SM_LOG_WOULDLOG -- Would something be logged?
**
**	Parameters:
**		lctx -- log context
**		category -- category
**		module -- module
**		level -- log level
**
**	Returns:
**		SM_SUCCESS except for (un)lock errors
*/

bool
sm_log_wouldlog(sm_log_ctx_P lctx, sm_logcategory_P category, sm_logmodule_P module, uint level)
{
	SM_IS_LOGCTX(lctx);

	if (NULL == category || 0 == category->smlc_log_level)
		if (NULL == module || 0 == module->smlm_log_level)
			return lctx->lctx_dbg_level >= level;
		else
			return module->smlm_log_level >= level;
	else
		if (NULL == module || 0 == module->smlm_log_level)
			return category->smlc_log_level >= level;
		else
			return module->smlm_log_level >= level &&
				category->smlc_log_level >= level;
}

#if 0
sm_ret_T
sm_log_setduplicateinterval(sm_logconfig_P lcfg, uint interval)
{
	SM_IS_LOGCFG(lcfg);

	return SM_SUCCESS;
}

uint
sm_log_getduplicateinterval(sm_logconfig_P lcfg)
{
	SM_IS_LOGCFG(lcfg);

	return SM_SUCCESS;
}
#endif /* 0 */

/* ARGSUSED1 */
sm_ret_T
sm_log_settag(sm_logconfig_P lcfg, const char *tag)
{
	SM_IS_LOGCFG(lcfg);

	return SM_SUCCESS;
}

sm_ret_T
sm_log_gettag(sm_logconfig_P lcfg)
{
	SM_IS_LOGCFG(lcfg);

	return SM_SUCCESS;
}

sm_ret_T
sm_log_opensyslog(const char *tag, int options, int facility)
{
	openlog(tag, options, facility);
	return SM_SUCCESS;
}

sm_ret_T
sm_log_closesyslog(void)
{
	closelog();
	return SM_SUCCESS;
}

sm_ret_T
sm_log_closefilelogs(sm_log_ctx_P lctx)
{
	SM_IS_LOGCTX(lctx);

	return SM_SUCCESS;
}

sm_ret_T
sm_log_categorybyname(sm_log_ctx_P lctx, const char *name, sm_logcategory_P *pcategories)
{
	uint ul;
	sm_logcategory_P categories;

	SM_IS_LOGCTX(lctx);
	SM_REQUIRE(name != NULL);
	SM_REQUIRE(pcategories != NULL);
	for (ul = 0; ul < SM_ARRAY_SIZE(lctx->lctx_cats); ul++) {
		categories = lctx->lctx_cats[ul];
		if (NULL == categories)
			continue;
		while (categories->smlc_name != NULL) {
			if (strcmp(name, categories->smlc_name) == 0) {
				*pcategories = categories;
				return SM_SUCCESS;
			}
			++categories;
		}
	}
	return sm_error_perm(SM_EM_LOG, SM_E_NOTFOUND);
}

sm_ret_T
sm_log_modulebyname(sm_log_ctx_P lctx, const char *name, sm_logmodule_P *pmodules)
{
	uint u;
	sm_logmodule_P modules;

	SM_IS_LOGCTX(lctx);
	SM_REQUIRE(name != NULL);
	SM_REQUIRE(pmodules != NULL);
	for (u = 0; u < SM_ARRAY_SIZE(lctx->lctx_mods); u++) {
		modules = lctx->lctx_mods[u];
		if (NULL == modules)
			continue;
		while (modules->smlm_name != NULL) {
			if (strcmp(name, modules->smlm_name) == 0) {
				*pmodules = modules;
				return SM_SUCCESS;
			}
			++modules;
		}
	}
	return sm_error_perm(SM_EM_LOG, SM_E_NOTFOUND);
}

sm_ret_T
sm_log_setcontext(sm_log_ctx_P lctx)
{
	SM_IS_LOGCTX(lctx);

	return SM_SUCCESS;
}

/*
**  SM_LOG_CAT_SET_LEVEL -- Set log level per category
**
**	Parameters:
**		lctx -- log context
**		name -- name of category
**		format -- format
**
**	Returns:
**		usual sm_error code.
**
**	Locking: none
*/

sm_ret_T
sm_log_cat_set_level(sm_log_ctx_P lctx, const char *name, uint level)
{
	sm_ret_T ret;
	sm_logcategory_P categories;

	SM_IS_LOGCTX(lctx);
	SM_REQUIRE(name != NULL);
	ret = sm_log_categorybyname(lctx, name, &categories);
	if (sm_is_err(ret))
		return ret;
	SM_ASSERT(categories != NULL);
	categories->smlc_log_level = level;
	return SM_SUCCESS;
}

sm_ret_T
sm_log_mod_set_level(sm_log_ctx_P lctx, const char *name, uint level)
{
	sm_ret_T ret;
	sm_logmodule_P modules;

	SM_IS_LOGCTX(lctx);
	SM_REQUIRE(name != NULL);
	ret = sm_log_modulebyname(lctx, name, &modules);
	if (sm_is_err(ret))
		return ret;
	SM_ASSERT(modules != NULL);
	modules->smlm_log_level = level;
	return SM_SUCCESS;
}


syntax highlighted by Code2HTML, v. 0.9.1