/*
 * This file is part of the zlog Library.
 *
 * Copyright (C) 2011 by Hardy Simpson <HardySimpson1984@gmail.com>
 *
 * Licensed under the LGPL v2.1, see the file COPYING in base directory.
 */

#include "fmacros.h"

#include <string.h>
#include <ctype.h>
#include <syslog.h>
#include <errno.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <pthread.h>

#include "rule.h"
#include "format.h"
#include "buf.h"
#include "thread.h"
#include "level_list.h"
#include "rotater.h"
#include "spec.h"
#include "conf.h"

#include "zc_defs.h"


void zlog_rule_profile(zlog_rule_t * a_rule, int flag)
{
	int i;
	zlog_spec_t *a_spec;

	zc_assert(a_rule,);
	zc_profile(flag, "---rule:[%p][%s%c%d]-[%d,%d][%s,%p,%d:%ld*%d~%s][%d][%d][%s:%s:%p];[%p]---",
		a_rule,

		a_rule->category,
		a_rule->compare_char,
		a_rule->level,

		a_rule->file_perms,
		a_rule->file_open_flags,

		a_rule->file_path,
		a_rule->dynamic_specs,
		a_rule->static_fd,

		a_rule->archive_max_size,
		a_rule->archive_max_count,
		a_rule->archive_path,

		a_rule->pipe_fd,

		a_rule->syslog_facility,

		a_rule->record_name,
		a_rule->record_path,
		a_rule->record_func,
		a_rule->format);

	if (a_rule->dynamic_specs) {
		zc_arraylist_foreach(a_rule->dynamic_specs, i, a_spec) {
			zlog_spec_profile(a_spec, flag);
		}
	}
	return;
}

/*******************************************************************************/

static int zlog_rule_output_static_file_single(zlog_rule_t * a_rule, zlog_thread_t * a_thread)
{
	struct stat stb;
	int do_file_reload = 0;
	int redo_inode_stat = 0;

	if (zlog_format_gen_msg(a_rule->format, a_thread)) {
		zc_error("zlog_format_gen_msg fail");
		return -1;
	}

	/* check if the output file was changed by an external tool by comparing the inode to our saved off one */
	if (stat(a_rule->file_path, &stb)) {
		if (errno != ENOENT) {
			zc_error("stat fail on [%s], errno[%d]", a_rule->file_path, errno);
			return -1;
		} else {
			do_file_reload = 1;
			redo_inode_stat = 1; /* we'll have to restat the newly created file to get the inode info */
		}
	} else {
		do_file_reload = (stb.st_ino != a_rule->static_ino || stb.st_dev != a_rule->static_dev);
	}

	if (do_file_reload) {
		close(a_rule->static_fd);
		a_rule->static_fd = open(a_rule->file_path,
			O_WRONLY | O_APPEND | O_CREAT | a_rule->file_open_flags,
			a_rule->file_perms);
		if (a_rule->static_fd < 0) {
			zc_error("open file[%s] fail, errno[%d]", a_rule->file_path, errno);
			return -1;
		}

		/* save off the new dev/inode info from the stat call we already did */
		if (redo_inode_stat) {
			if (stat(a_rule->file_path, &stb)) {
				zc_error("stat fail on new file[%s], errno[%d]", a_rule->file_path, errno);
				return -1;
			}
		}
		a_rule->static_dev = stb.st_dev;
		a_rule->static_ino = stb.st_ino;
	}

	if (write(a_rule->static_fd,
			zlog_buf_str(a_thread->msg_buf),
			zlog_buf_len(a_thread->msg_buf)) < 0) {
		zc_error("write fail, errno[%d]", errno);
		return -1;
	}

	/* not so thread safe here, as multiple thread may ++fsync_count at the same time */
	if (a_rule->fsync_period && ++a_rule->fsync_count >= a_rule->fsync_period) {
		a_rule->fsync_count = 0;
		if (fsync(a_rule->static_fd)) {
			zc_error("fsync[%d] fail, errno[%d]", a_rule->static_fd, errno);
		}
	}

	return 0;
}

static char * zlog_rule_gen_archive_path(zlog_rule_t *a_rule, zlog_thread_t *a_thread)
{
	int i;
	zlog_spec_t *a_spec;

	if (!a_rule->archive_specs) return a_rule->archive_path;

	zlog_buf_restart(a_thread->archive_path_buf);

	zc_arraylist_foreach(a_rule->archive_specs, i, a_spec) {
		if (zlog_spec_gen_archive_path(a_spec, a_thread)) {
			zc_error("zlog_spec_gen_path fail");
			return NULL;
		}
	}

	zlog_buf_seal(a_thread->archive_path_buf);
	return zlog_buf_str(a_thread->archive_path_buf);
}

static int zlog_rule_output_static_file_rotate(zlog_rule_t * a_rule, zlog_thread_t * a_thread)
{
	size_t len;
	struct zlog_stat info;
	int fd;

	if (zlog_format_gen_msg(a_rule->format, a_thread)) {
		zc_error("zlog_format_gen_msg fail");
		return -1;
	}

	fd = open(a_rule->file_path, 
		a_rule->file_open_flags | O_WRONLY | O_APPEND | O_CREAT, a_rule->file_perms);
	if (fd < 0) {
		zc_error("open file[%s] fail, errno[%d]", a_rule->file_path, errno);
		return -1;
	}

	len = zlog_buf_len(a_thread->msg_buf);
	if (write(fd, zlog_buf_str(a_thread->msg_buf), len) < 0) {
		zc_error("write fail, errno[%d]", errno);
		close(fd);
		return -1;
	}

	if (a_rule->fsync_period && ++a_rule->fsync_count >= a_rule->fsync_period) {
		a_rule->fsync_count = 0;
		if (fsync(fd)) zc_error("fsync[%d] fail, errno[%d]", fd, errno);
	}

	if (close(fd) < 0) {
		zc_error("close fail, maybe cause by write, errno[%d]", errno);
		return -1;
	}

	if (len > a_rule->archive_max_size) {
		zc_debug("one msg's len[%ld] > archive_max_size[%ld], no rotate",
			 (long)len, (long)a_rule->archive_max_size);
		return 0;
	}

	if (stat(a_rule->file_path, &info)) {
		zc_warn("stat [%s] fail, errno[%d], maybe in rotating", a_rule->file_path, errno);
		return 0;
	}

	/* file not so big, return */
	if (info.st_size + len < a_rule->archive_max_size) return 0;

	if (zlog_rotater_rotate(zlog_env_conf->rotater, 
		a_rule->file_path, len,
		zlog_rule_gen_archive_path(a_rule, a_thread),
		a_rule->archive_max_size, a_rule->archive_max_count)
		) {
		zc_error("zlog_rotater_rotate fail");
		return -1;
	} /* success or no rotation do nothing */

	return 0;
}

/* return path	success
 * return NULL	fail
 */
#define zlog_rule_gen_path(a_rule, a_thread) do {    \
	int i;    \
	zlog_spec_t *a_spec;    \
    \
	zlog_buf_restart(a_thread->path_buf);    \
    \
	zc_arraylist_foreach(a_rule->dynamic_specs, i, a_spec) {    \
		if (zlog_spec_gen_path(a_spec, a_thread)) {    \
			zc_error("zlog_spec_gen_path fail");    \
			return -1;    \
		}    \
	}    \
    \
	zlog_buf_seal(a_thread->path_buf);    \
} while(0)


static int zlog_rule_output_dynamic_file_single(zlog_rule_t * a_rule, zlog_thread_t * a_thread)
{
	int fd;

	zlog_rule_gen_path(a_rule, a_thread);

	if (zlog_format_gen_msg(a_rule->format, a_thread)) {
		zc_error("zlog_format_output fail");
		return -1;
	}

	fd = open(zlog_buf_str(a_thread->path_buf),
		a_rule->file_open_flags | O_WRONLY | O_APPEND | O_CREAT, a_rule->file_perms);
	if (fd < 0) {
		zc_error("open file[%s] fail, errno[%d]", zlog_buf_str(a_thread->path_buf), errno);
		return -1;
	}

	if (write(fd, zlog_buf_str(a_thread->msg_buf), zlog_buf_len(a_thread->msg_buf)) < 0) {
		zc_error("write fail, errno[%d]", errno);
		close(fd);
		return -1;
	}

	if (a_rule->fsync_period && ++a_rule->fsync_count >= a_rule->fsync_period) {
		a_rule->fsync_count = 0;
		if (fsync(fd)) zc_error("fsync[%d] fail, errno[%d]", fd, errno);
	}

	if (close(fd) < 0) {
		zc_error("close fail, maybe cause by write, errno[%d]", errno);
		return -1;
	}

	return 0;
}

static int zlog_rule_output_dynamic_file_rotate(zlog_rule_t * a_rule, zlog_thread_t * a_thread)
{
	int fd;
	char *path;
	size_t len;
	struct zlog_stat info;

	zlog_rule_gen_path(a_rule, a_thread);

	if (zlog_format_gen_msg(a_rule->format, a_thread)) {
		zc_error("zlog_format_output fail");
		return -1;
	}

	path = zlog_buf_str(a_thread->path_buf);
	fd = open(path, a_rule->file_open_flags | O_WRONLY | O_APPEND | O_CREAT, a_rule->file_perms);
	if (fd < 0) {
		zc_error("open file[%s] fail, errno[%d]", zlog_buf_str(a_thread->path_buf), errno);
		return -1;
	}

	len = zlog_buf_len(a_thread->msg_buf);
	if (write(fd, zlog_buf_str(a_thread->msg_buf), len) < 0) {
		zc_error("write fail, errno[%d]", errno);
		close(fd);
		return -1;
	}

	if (a_rule->fsync_period && ++a_rule->fsync_count >= a_rule->fsync_period) {
		a_rule->fsync_count = 0;
		if (fsync(fd)) zc_error("fsync[%d] fail, errno[%d]", fd, errno);
	}

	if (close(fd) < 0) {
		zc_error("write fail, maybe cause by write, errno[%d]", errno);
		return -1;
	}

	if (len > a_rule->archive_max_size) {
		zc_debug("one msg's len[%ld] > archive_max_size[%ld], no rotate",
			 (long)len, (long) a_rule->archive_max_size);
		return 0;
	}

	if (stat(path, &info)) {
		zc_warn("stat [%s] fail, errno[%d], maybe in rotating", path, errno);
		return 0;
	}

	/* file not so big, return */
	if (info.st_size + len < a_rule->archive_max_size) return 0;

	if (zlog_rotater_rotate(zlog_env_conf->rotater, 
		path, len,
		zlog_rule_gen_archive_path(a_rule, a_thread),
		a_rule->archive_max_size, a_rule->archive_max_count)
		) {
		zc_error("zlog_rotater_rotate fail");
		return -1;
	} /* success or no rotation do nothing */

	return 0;
}

static int zlog_rule_output_pipe(zlog_rule_t * a_rule, zlog_thread_t * a_thread)
{
	if (zlog_format_gen_msg(a_rule->format, a_thread)) {
		zc_error("zlog_format_gen_msg fail");
		return -1;
	}

	if (write(a_rule->pipe_fd,
			zlog_buf_str(a_thread->msg_buf),
			zlog_buf_len(a_thread->msg_buf)) < 0) {
		zc_error("write fail, errno[%d]", errno);
		return -1;
	}

	return 0;
}

static int zlog_rule_output_syslog(zlog_rule_t * a_rule, zlog_thread_t * a_thread)
{
	zlog_level_t *a_level;

	if (zlog_format_gen_msg(a_rule->format, a_thread)) {
		zc_error("zlog_format_gen_msg fail");
		return -1;
	}

	/*
	msg = a_thread->msg_buf->start;
	msg_len = a_thread->msg_buf->end - a_thread->msg_buf->start;
	 */

	a_level = zlog_level_list_get(zlog_env_conf->levels, a_thread->event->level);
	zlog_buf_seal(a_thread->msg_buf);
	syslog(a_rule->syslog_facility | a_level->syslog_level,
		"%s",  zlog_buf_str(a_thread->msg_buf));
	return 0;
}

static int zlog_rule_output_static_record(zlog_rule_t * a_rule, zlog_thread_t * a_thread)
{
	zlog_msg_t msg;

	if (!a_rule->record_func) {
		zc_error("user defined record funcion for [%s] not set, no output",
			a_rule->record_name);
		return -1;
	}

	if (zlog_format_gen_msg(a_rule->format, a_thread)) {
		zc_error("zlog_format_gen_msg fail");
		return -1;
	}
	zlog_buf_seal(a_thread->msg_buf);

	msg.buf = zlog_buf_str(a_thread->msg_buf);
	msg.len = zlog_buf_len(a_thread->msg_buf);
	msg.path = a_rule->record_path;

	if (a_rule->record_func(&msg)) {
		zc_error("a_rule->record fail");
		return -1;
	}
	return 0;
}

static int zlog_rule_output_dynamic_record(zlog_rule_t * a_rule, zlog_thread_t * a_thread)
{
	zlog_msg_t msg;

	if (!a_rule->record_func) {
		zc_error("user defined record funcion for [%s] not set, no output",
			a_rule->record_name);
		return -1;
	}

	zlog_rule_gen_path(a_rule, a_thread);

	if (zlog_format_gen_msg(a_rule->format, a_thread)) {
		zc_error("zlog_format_gen_msg fail");
		return -1;
	}
	zlog_buf_seal(a_thread->msg_buf);

	msg.buf = zlog_buf_str(a_thread->msg_buf);
	msg.len = zlog_buf_len(a_thread->msg_buf);
	msg.path = zlog_buf_str(a_thread->path_buf);

	if (a_rule->record_func(&msg)) {
		zc_error("a_rule->record fail");
		return -1;
	}
	return 0;
}

static int zlog_rule_output_stdout(zlog_rule_t * a_rule,
				   zlog_thread_t * a_thread)
{

	if (zlog_format_gen_msg(a_rule->format, a_thread)) {
		zc_error("zlog_format_gen_msg fail");
		return -1;
	}

	if (write(STDOUT_FILENO,
		zlog_buf_str(a_thread->msg_buf), zlog_buf_len(a_thread->msg_buf)) < 0) {
		zc_error("write fail, errno[%d]", errno);
		return -1;
	}

	return 0;
}

static int zlog_rule_output_stderr(zlog_rule_t * a_rule,
				   zlog_thread_t * a_thread)
{

	if (zlog_format_gen_msg(a_rule->format, a_thread)) {
		zc_error("zlog_format_gen_msg fail");
		return -1;
	}

	if (write(STDERR_FILENO,
		zlog_buf_str(a_thread->msg_buf), zlog_buf_len(a_thread->msg_buf)) < 0) {
		zc_error("write fail, errno[%d]", errno);
		return -1;
	}

	return 0;
}
/*******************************************************************************/
static int syslog_facility_atoi(char *facility)
{
	/* guess no unix system will choose -187
	 * as its syslog facility, so it is a safe return value
	 */
	zc_assert(facility, -187);

	if (STRICMP(facility, ==, "LOG_LOCAL0")) return LOG_LOCAL0;
	if (STRICMP(facility, ==, "LOG_LOCAL1")) return LOG_LOCAL1;
	if (STRICMP(facility, ==, "LOG_LOCAL2")) return LOG_LOCAL2;
	if (STRICMP(facility, ==, "LOG_LOCAL3")) return LOG_LOCAL3;
	if (STRICMP(facility, ==, "LOG_LOCAL4")) return LOG_LOCAL4;
	if (STRICMP(facility, ==, "LOG_LOCAL5")) return LOG_LOCAL5;
	if (STRICMP(facility, ==, "LOG_LOCAL6")) return LOG_LOCAL6;
	if (STRICMP(facility, ==, "LOG_LOCAL7")) return LOG_LOCAL7;
	if (STRICMP(facility, ==, "LOG_USER")) return LOG_USER;
	if (STRICMP(facility, ==, "LOG_AUTHPRIV")) return LOG_AUTHPRIV;
	if (STRICMP(facility, ==, "LOG_CRON")) return LOG_CRON;
	if (STRICMP(facility, ==, "LOG_DAEMON")) return LOG_DAEMON;
	if (STRICMP(facility, ==, "LOG_FTP")) return LOG_FTP;
	if (STRICMP(facility, ==, "LOG_KERN")) return LOG_KERN;
	if (STRICMP(facility, ==, "LOG_LPR")) return LOG_LPR;
	if (STRICMP(facility, ==, "LOG_MAIL")) return LOG_MAIL;
	if (STRICMP(facility, ==, "LOG_NEWS")) return LOG_NEWS;
	if (STRICMP(facility, ==, "LOG_SYSLOG")) return LOG_SYSLOG;
		return LOG_AUTHPRIV;

	zc_error("wrong syslog facility[%s], must in LOG_LOCAL[0-7] or LOG_USER", facility);
	return -187;
}

static int zlog_rule_parse_path(char *path_start, /* start with a " */
		char *path_str, size_t path_size, zc_arraylist_t **path_specs,
		int *time_cache_count)
{
	char *p, *q;
	size_t len;
	zlog_spec_t *a_spec;
	zc_arraylist_t *specs;

	p = path_start + 1;

	q = strrchr(p, '"');
	if (!q) {
		zc_error("matching \" not found in conf line[%s]", path_start);
		return -1;
	}
	len = q - p;
	if (len > path_size - 1) {
		zc_error("file_path too long %ld > %ld", len, path_size - 1);
		return -1;
	}
	memcpy(path_str, p, len);

	/* replace any environment variables like %E(HOME) */
	if (zc_str_replace_env(path_str, path_size)) {
		zc_error("zc_str_replace_env fail");
		return -1;
	}

	if (strchr(path_str, '%') == NULL) {
		/* static, no need create specs */
		return 0;
	}

	specs = zc_arraylist_new((zc_arraylist_del_fn)zlog_spec_del);
	if (!path_specs) {
		zc_error("zc_arraylist_new fail");
		return -1;
	}

	for (p = path_str; *p != '\0'; p = q) {
		a_spec = zlog_spec_new(p, &q, time_cache_count);
		if (!a_spec) {
			zc_error("zlog_spec_new fail");
			goto err;
		}

		if (zc_arraylist_add(specs, a_spec)) {
			zc_error("zc_arraylist_add fail");
			goto err;
		}
	}

	*path_specs = specs;
	return 0;
err:
	if (specs) zc_arraylist_del(specs);
	if (a_spec) zlog_spec_del(a_spec);
	return -1;
}

zlog_rule_t *zlog_rule_new(char *line,
		zc_arraylist_t *levels,
		zlog_format_t * default_format,
		zc_arraylist_t * formats,
		unsigned int file_perms,
		size_t fsync_period,
		int * time_cache_count)
{
	int rc = 0;
	int nscan = 0;
	int nread = 0;
	zlog_rule_t *a_rule;

	char selector[MAXLEN_CFG_LINE + 1];
	char category[MAXLEN_CFG_LINE + 1];
	char level[MAXLEN_CFG_LINE + 1];

	char *action;
	char output[MAXLEN_CFG_LINE + 1];
	char format_name[MAXLEN_CFG_LINE + 1];
	char file_path[MAXLEN_CFG_LINE + 1];
	char archive_max_size[MAXLEN_CFG_LINE + 1];
	char *file_limit;

	char *p;
	char *q;
	size_t len;

	zc_assert(line, NULL);
	zc_assert(default_format, NULL);
	zc_assert(formats, NULL);

	a_rule = calloc(1, sizeof(zlog_rule_t));
	if (!a_rule) {
		zc_error("calloc fail, errno[%d]", errno);
		return NULL;
	}

	a_rule->file_perms = file_perms;
	a_rule->fsync_period = fsync_period;

	/* line         [f.INFO "%H/log/aa.log", 20MB * 12; MyTemplate]
	 * selector     [f.INFO]
	 * *action      ["%H/log/aa.log", 20MB * 12; MyTemplate]
	 */
	memset(&selector, 0x00, sizeof(selector));
	nscan = sscanf(line, "%s %n", selector, &nread);
	if (nscan != 1) {
		zc_error("sscanf [%s] fail, selector", line);
		goto err;
	}
	action = line + nread;

	/*
	 * selector     [f.INFO]
	 * category     [f]
	 * level        [.INFO]
	 */
	memset(category, 0x00, sizeof(category));
	memset(level, 0x00, sizeof(level));
	nscan = sscanf(selector, " %[^.].%s", category, level);
	if (nscan != 2) {
		zc_error("sscanf [%s] fail, category or level is null",
			 selector);
		goto err;
	}

	/* check and set category */
	for (p = category; *p != '\0'; p++) {
		if ((!isalnum(*p)) && (*p != '_') && (*p != '-') && (*p != '*') && (*p != '!')) {
			zc_error("category name[%s] character is not in [a-Z][0-9][_!*-]", category);
			goto err;
		}
	}

	/* as one line can't be longer than MAXLEN_CFG_LINE, same as category */
	strcpy(a_rule->category, category);

	/* check and set level */
	switch (level[0]) {
	case '=':
		/* aa.=debug */
		a_rule->compare_char = '=';
		p = level + 1;
		break;
	case '!':
		/* aa.!debug */
		a_rule->compare_char = '!';
		p = level + 1;
		break;
	case '*':
		/* aa.* */
		a_rule->compare_char = '*';
		p = level;
		break;
	default:
		/* aa.debug */
		a_rule->compare_char = '.';
		p = level;
		break;
	}

	a_rule->level = zlog_level_list_atoi(levels, p);

	/* level_bit is a bitmap represents which level can be output 
	 * 32bytes, [0-255] levels, see level.c
	 * which bit field is 1 means allow output and 0 not
	 */
	switch (a_rule->compare_char) {
	case '=':
		memset(a_rule->level_bitmap, 0x00, sizeof(a_rule->level_bitmap));
		a_rule->level_bitmap[a_rule->level / 8] |= (1 << (7 - a_rule->level % 8));
		break;
	case '!':
		memset(a_rule->level_bitmap, 0xFF, sizeof(a_rule->level_bitmap));
		a_rule->level_bitmap[a_rule->level / 8] &= ~(1 << (7 - a_rule->level % 8));
		break;
	case '*':
		memset(a_rule->level_bitmap, 0xFF, sizeof(a_rule->level_bitmap));
		break;
	case '.':
		memset(a_rule->level_bitmap, 0x00, sizeof(a_rule->level_bitmap));
		a_rule->level_bitmap[a_rule->level / 8] |= ~(0xFF << (8 - a_rule->level % 8));
		memset(a_rule->level_bitmap + a_rule->level / 8 + 1, 0xFF,
				sizeof(a_rule->level_bitmap) -  a_rule->level / 8 - 1);
		break;
	}

	/* action               ["%H/log/aa.log", 20MB * 12 ; MyTemplate]
	 * output               ["%H/log/aa.log", 20MB * 12]
	 * format               [MyTemplate]
	 */
	memset(output, 0x00, sizeof(output));
	memset(format_name, 0x00, sizeof(format_name));
	nscan = sscanf(action, " %[^;];%s", output, format_name);
	if (nscan < 1) {
		zc_error("sscanf [%s] fail", action);
		goto err;
	}

	/* check and get format */
	if (STRCMP(format_name, ==, "")) {
		zc_debug("no format specified, use default");
		a_rule->format = default_format;
	} else {
		int i;
		int find_flag = 0;
		zlog_format_t *a_format;

		zc_arraylist_foreach(formats, i, a_format) {
			if (zlog_format_has_name(a_format, format_name)) {
				a_rule->format = a_format;
				find_flag = 1;
				break;
			}
		}
		if (!find_flag) {
			zc_error("in conf file can't find format[%s], pls check",
			     format_name);
			goto err;
		}
	}

	/* output               [-"%E(HOME)/log/aa.log" , 20MB*12]  [>syslog , LOG_LOCAL0 ]
	 * file_path            [-"%E(HOME)/log/aa.log" ]           [>syslog ]
	 * *file_limit          [20MB * 12 ~ "aa.#i.log" ]          [LOG_LOCAL0]
	 */
	memset(file_path, 0x00, sizeof(file_path));
	nscan = sscanf(output, " %[^,],", file_path);
	if (nscan < 1) {
		zc_error("sscanf [%s] fail", action);
		goto err;
	}

	file_limit = strchr(output, ',');
	if (file_limit) {
		file_limit++; /* skip the , */
		while( isspace(*file_limit) ) {
			file_limit++;
		}
	}

	p = NULL;
	switch (file_path[0]) {
	case '-' :
		/* sync file each time write log */
		if (file_path[1] != '"') {
			zc_error(" - must set before a file output");
			goto err;
		}

		/* no need to fsync, as file is opened by O_SYNC, write immediately */
		a_rule->fsync_period = 0;

		p = file_path + 1;
		a_rule->file_open_flags = O_SYNC;
		/* fall through */
	case '"' :
		if (!p) p = file_path;

		rc = zlog_rule_parse_path(p, a_rule->file_path, sizeof(a_rule->file_path),
				&(a_rule->dynamic_specs), time_cache_count);
		if (rc) {
			zc_error("zlog_rule_parse_path fail");
			goto err;
		}

		if (file_limit) {
			memset(archive_max_size, 0x00, sizeof(archive_max_size));
			nscan = sscanf(file_limit, " %[0-9MmKkBb] * %d ~",
					archive_max_size, &(a_rule->archive_max_count));
			if (nscan) {
				a_rule->archive_max_size = zc_parse_byte_size(archive_max_size);
			}
			p = strchr(file_limit, '"');
			if (p) { /* archive file path exist */
				rc = zlog_rule_parse_path(p,
					a_rule->archive_path, sizeof(a_rule->file_path),
					&(a_rule->archive_specs), time_cache_count);
				if (rc) {
					zc_error("zlog_rule_parse_path fail");
					goto err;
				}

				p = strchr(a_rule->archive_path, '#');
				if ( (p == NULL) || ((strchr(p, 'r') == NULL) && (strchr(p, 's') == NULL))) {
					zc_error("archive_path must contain #r or #s");
					goto err;
				}
			}
		}

		/* try to figure out if the log file path is dynamic or static */
		if (a_rule->dynamic_specs) {
			if (a_rule->archive_max_size <= 0) {
				a_rule->output = zlog_rule_output_dynamic_file_single;
			} else {
				a_rule->output = zlog_rule_output_dynamic_file_rotate;
			}
		} else {
			struct stat stb;

			if (a_rule->archive_max_size <= 0) {
				a_rule->output = zlog_rule_output_static_file_single;
			} else {
				/* as rotate, so need to reopen everytime */
				a_rule->output = zlog_rule_output_static_file_rotate;
			}

			a_rule->static_fd = open(a_rule->file_path,
				O_WRONLY | O_APPEND | O_CREAT | a_rule->file_open_flags,
				a_rule->file_perms);
			if (a_rule->static_fd < 0) {
				zc_error("open file[%s] fail, errno[%d]", a_rule->file_path, errno);
				goto err;
			}

			/* save off the inode information for checking for a changed file later on */
			if (fstat(a_rule->static_fd, &stb)) {
				zc_error("stat [%s] fail, errno[%d], failing to open static_fd", a_rule->file_path, errno);
				goto err;
			}

			if (a_rule->archive_max_size > 0) {
				close(a_rule->static_fd);
				a_rule->static_fd = -1;
			}

			a_rule->static_dev = stb.st_dev;
			a_rule->static_ino = stb.st_ino;
		}
		break;
	case '|' :
		a_rule->pipe_fp = popen(output + 1, "w");
		if (!a_rule->pipe_fp) {
			zc_error("popen fail, errno[%d]", errno);
			goto err;
		}
		a_rule->pipe_fd = fileno(a_rule->pipe_fp);
		if (a_rule->pipe_fd < 0 ) {
			zc_error("fileno fail, errno[%d]", errno);
			goto err;
		}
		a_rule->output = zlog_rule_output_pipe;
		break;
	case '>' :
		if (STRNCMP(file_path + 1, ==, "syslog", 6)) {
			a_rule->syslog_facility = syslog_facility_atoi(file_limit);
			if (a_rule->syslog_facility == -187) {
				zc_error("-187 get");
				goto err;
			}
			a_rule->output = zlog_rule_output_syslog;
			openlog(NULL, LOG_NDELAY | LOG_NOWAIT | LOG_PID, LOG_USER);
		} else if (STRNCMP(file_path + 1, ==, "stdout", 6)) {
			a_rule->output = zlog_rule_output_stdout;
		} else if (STRNCMP(file_path + 1, ==, "stderr", 6)) {
			a_rule->output = zlog_rule_output_stderr;
		} else {
			zc_error
			    ("[%s]the string after is not syslog, stdout or stderr", output);
			goto err;
		}
		break;
	case '$' :
		sscanf(file_path + 1, "%s", a_rule->record_name);
			
		if (file_limit) {  /* record path exists */
			p = strchr(file_limit, '"');
			if (!p) {
				zc_error("record_path not start with \", [%s]", file_limit);
				goto err;
			}
			p++; /* skip 1st " */

			q = strrchr(p, '"');
			if (!q) {
				zc_error("matching \" not found in conf line[%s]", p);
				goto err;
			}
			len = q - p;
			if (len > sizeof(a_rule->record_path) - 1) {
				zc_error("record_path too long %ld > %ld", len, sizeof(a_rule->record_path) - 1);
				goto err;
			}
			memcpy(a_rule->record_path, p, len);
		}

		/* replace any environment variables like %E(HOME) */
		rc = zc_str_replace_env(a_rule->record_path, sizeof(a_rule->record_path));
		if (rc) {
			zc_error("zc_str_replace_env fail");
			goto err;
		}

		/* try to figure out if the log file path is dynamic or static */
		if (strchr(a_rule->record_path, '%') == NULL) {
			a_rule->output = zlog_rule_output_static_record;
		} else {
			zlog_spec_t *a_spec;

			a_rule->output = zlog_rule_output_dynamic_record;

			a_rule->dynamic_specs = zc_arraylist_new((zc_arraylist_del_fn)zlog_spec_del);
			if (!(a_rule->dynamic_specs)) {
				zc_error("zc_arraylist_new fail");
				goto err;
			}
			for (p = a_rule->record_path; *p != '\0'; p = q) {
				a_spec = zlog_spec_new(p, &q, time_cache_count);
				if (!a_spec) {
					zc_error("zlog_spec_new fail");
					goto err;
				}

				rc = zc_arraylist_add(a_rule->dynamic_specs, a_spec);
				if (rc) {
					zlog_spec_del(a_spec);
					zc_error("zc_arraylist_add fail");
					goto err;
				}
			}
		}
		break;
	default :
		zc_error("the 1st char[%c] of file_path[%s] is wrong",
		       file_path[0], file_path);
		goto err;
	}

	return a_rule;
err:
	zlog_rule_del(a_rule);
	return NULL;
}

void zlog_rule_del(zlog_rule_t * a_rule)
{
	zc_assert(a_rule,);
	if (a_rule->dynamic_specs) {
		zc_arraylist_del(a_rule->dynamic_specs);
		a_rule->dynamic_specs = NULL;
	}
	if (a_rule->static_fd > 0) {
		if (close(a_rule->static_fd)) {
			zc_error("close fail, maybe cause by write, errno[%d]", errno);
		}
	}
	if (a_rule->pipe_fp) {
		if (pclose(a_rule->pipe_fp) == -1) {
			zc_error("pclose fail, errno[%d]", errno);
		}
	}
	if (a_rule->archive_specs) {
		zc_arraylist_del(a_rule->archive_specs);
		a_rule->archive_specs = NULL;
	}
	zc_debug("zlog_rule_del[%p]", a_rule);
    free(a_rule);
	return;
}

/*******************************************************************************/
int zlog_rule_output(zlog_rule_t * a_rule, zlog_thread_t * a_thread)
{
	switch (a_rule->compare_char) {
	case '*' :
		return a_rule->output(a_rule, a_thread);
		break;
	case '.' :
		if (a_thread->event->level >= a_rule->level) {
			return a_rule->output(a_rule, a_thread);
		} else {
			return 0;
		}
		break;
	case '=' :
		if (a_thread->event->level == a_rule->level) {
			return a_rule->output(a_rule, a_thread);
		} else {
			return 0;
		}
		break;
	case '!' :
		if (a_thread->event->level != a_rule->level) {
			return a_rule->output(a_rule, a_thread);
		} else {
			return 0;
		}
		break;
	}

	return 0;
}

/*******************************************************************************/
int zlog_rule_is_wastebin(zlog_rule_t * a_rule)
{
	zc_assert(a_rule, -1);
	
	if (STRCMP(a_rule->category, ==, "!")) {
		return 1;
	}

	return 0;
}

/*******************************************************************************/
int zlog_rule_match_category(zlog_rule_t * a_rule, char *category)
{
	zc_assert(a_rule, -1);
	zc_assert(category, -1);

	if (STRCMP(a_rule->category, ==, "*")) {
		/* '*' match anything, so go on */
		return 1;
	} else if (STRCMP(a_rule->category, ==, category)) {
		/* accurate compare */
		return 1;
	} else {
		/* aa_ match aa_xx & aa, but not match aa1_xx */
		size_t len;
		len = strlen(a_rule->category);

		if (a_rule->category[len - 1] == '_') {
			if (strlen(category) == len - 1) {
				len--;
			}

			if (STRNCMP(a_rule->category, ==, category, len)) {
				return 1;
			}
		}
	}

	return 0;
}

/*******************************************************************************/

int zlog_rule_set_record(zlog_rule_t * a_rule, zc_hashtable_t *records)
{
	zlog_record_t *a_record;

	if (a_rule->output != zlog_rule_output_static_record 
	&&  a_rule->output != zlog_rule_output_dynamic_record) {
		return 0; /* fliter, may go through not record rule */
	}

	a_record = zc_hashtable_get(records, a_rule->record_name);
	if (a_record) {
		a_rule->record_func = a_record->output;
	}
	return 0;
}
