/* vi: set sw=4 ts=4: */
/*
 * Mini syslogd implementation for busybox
 *
 * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org>
 *
 * Copyright (C) 2000 by Karl M. Hegbloom <karlheg@debian.org>
 *
 * Copyright (C) 2007 by Rupert Li <rupert@amit.com.tw>
 *
 * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
 */

#include "ringlog.h"

static const char *ringlogIndexPath = "/var/log/rlog.idx";
static const char *ringlogMessagePath = "/var/log/rlog.msg";

static struct {
	pid_t pid;
	int sig;
	FILE *fp;
} subscriber;

//#define DEBUG

static void save_log(int pri, const char *msg, int msg_len)
{
	int fi = open(ringlogIndexPath, O_RDWR | O_CREAT | O_NONBLOCK);

	if (fi >= 0) {
		RLOG_INDEX_HEADER header;
		RLOG_INDEX_ENTRY entry;
		struct sysinfo info;
		struct flock fl;
		int fm;		//file handle for ring log message
		int len_1;	//msg_len + 1

		//get current uptime
		sysinfo(&info);

		//lock file
		fl.l_whence = SEEK_SET;
		fl.l_start = 0;
		fl.l_len = sizeof(header);
		fl.l_type = F_WRLCK;
		fcntl(fi, F_SETLKW, &fl);

		/* read the header to get status of ringlog */

		if (read(fi, &header, sizeof(header)) != sizeof(header)) {
			/* make an initial copy */
			memset(&header, 0, sizeof(header));
			header.magic = RLOG_HEADER_MAGIC;
			header.version = RLOG_HEADER_VERSION;
			header.entry_count = RLOG_CONF_ENTRY_COUNT;	//for reader's reference
			//header.log_count = 0;
			header.lid_base = 1;	//first log id must be '1'
			//header.log_begin = 0;
			//header.log_end = 0;
			//header.mb_end = 0;
			//header.mb_size = 0;
		}

		/* decide where to write the message? */

		len_1 = msg_len + 1; //include '\0'

		if (header.mb_end + len_1 >= RLOG_CONF_MSG_BUFFER_SIZE) {

			int last = (header.log_end == 0) ? (RLOG_CONF_ENTRY_COUNT - 1)  : (header.log_end - 1);
			lseek(fi, sizeof(header) + sizeof(entry) * last, SEEK_SET);
			read(fi, &entry, sizeof(entry));
			entry.buf_used += (RLOG_CONF_MSG_BUFFER_SIZE - header.mb_end);
			lseek(fi, sizeof(header) + sizeof(entry) * last, SEEK_SET);
			write(fi, &entry, sizeof(entry));
#ifdef DEBUG
			fprintf(stderr, "RingLog: remainded space (%d) is not enough for %d bytes\n",
				(RLOG_CONF_MSG_BUFFER_SIZE - header.mb_end), len_1);
#endif
			header.mb_size += (RLOG_CONF_MSG_BUFFER_SIZE - header.mb_end);
			header.mb_end = 0;
		}

		/* write the message */

		fm = open(ringlogMessagePath, O_WRONLY | O_CREAT | O_NONBLOCK);
		lseek(fm, header.mb_end, SEEK_SET);
		write(fm, msg, len_1);
		close(fm);

		/* write the index */

		entry.time_stamp = info.uptime;
		entry.pri_facility = LOG_FAC(pri);
		entry.pri_severity = LOG_PRI(pri);
		entry.buf_used = len_1;
		entry.msg_offset = header.mb_end;

		lseek(fi, sizeof(header) + sizeof(entry) * header.log_end, SEEK_SET);
		write(fi, &entry, sizeof(entry));

		/* increase index ring */

		if (++header.log_end >= RLOG_CONF_ENTRY_COUNT)
			header.log_end = 0;
		header.log_count++;

		/* increase message ring */

		header.mb_end += len_1;
		header.mb_size += len_1;

		/* check if we have to recall some space ... */

		while (header.log_count > RLOG_CONF_MAX_LOG_COUNT || 
			   header.mb_size > RLOG_CONF_MAX_BUFFER_USED) {

			lseek(fi, sizeof(header) + sizeof(entry) * header.log_begin, SEEK_SET);
			read(fi, &entry, sizeof(entry));

			header.lid_base++;
			if (++header.log_begin >= RLOG_CONF_ENTRY_COUNT)
				header.log_begin = 0;
			header.mb_size -= entry.buf_used;
			header.log_count--;
#ifdef DEBUG
			fprintf(stderr, "RingLog: entry count=%d, buffer used=%d\n", header.log_count, header.mb_size);
#endif
		}

		/* update the header */

		lseek(fi, 0, SEEK_SET);
		write(fi, &header, sizeof(header));

		fl.l_type = F_UNLCK;
		fcntl(fi, F_SETLKW, &fl);
		close(fi);

#ifdef CONFIG_FEATURE_RLOG_FILTER
		if (test_filter(RLOG_FILTER_0, pri, msg))
		{
			//output log id to ipc socket to inform the peer
			if (subscriber.fp)
			{
				fprintf(subscriber.fp, "%ld\n", (unsigned long)(header.lid_base + header.log_count - 1));
				fflush(subscriber.fp);
				if (subscriber.pid > 0) kill(subscriber.pid, subscriber.sig);
			}
		}
#endif
	} else {
		/* Always send console messages to /dev/console so people will see them. */
		int fd = device_open(_PATH_CONSOLE, O_WRONLY | O_NOCTTY | O_NONBLOCK);
		if (fd >= 0) {
			dprintf(fd, "%s\n", msg);
			close(fd);
		} else {
			fprintf(stderr, "Bummer, can't print: %s\n", msg);
			fflush(stderr);
		}
	}
}

static void logMessage(int pri, char *msg)
{
	int msg_len = strlen(msg);

	if (msg_len <= 16) 
	{
#ifdef DEBUG
		fprintf(stderr, "RingLog: unexpected log format: pri=0%o msg='%s'\n", pri, msg);
#endif
		return;
	}

	/* discard the time stamp */
	msg += 16;
	msg_len -= 16;

#ifdef CONFIG_FEATURE_RLOG_FILTER
	/* is it a ringlog command? */
	if (pri == 888)
	{
		int rc = -1;

		//try to locate the ringlog command
		//-------------------------------
		// XXXX: cc pppp
		//-------------------------------
		// "XXXX" is ignored
		// "cc" is ringlog command
		// "pppp" is ringlog parameter (at least one byte)
		char *cmd = strchr(msg, ':');
		if (cmd && ((cmd + 6 - msg) <= msg_len))
		{
			cmd += 2;
			switch (cmd[0])
			{
			case 'f': //Filter's setting, ex: "fc F=...", "fn F=...", "f0 F=..."
				rc = set_filter_cmd(cmd);
				break;
			case 's': //misc Setting, ex: "sc ...", "sd ..."
				switch (cmd[1])
				{
				case 'c': //syslog collector, ex: "sc 192.168.123.254:1234"
					{
						int port = 514;
						char *host = cmd + 3;
						char *p = strchr(host, ':');
						if (p) {
							port = xatou16(p + 1);
							*p = '\0';
						}
						if (remotefd != -1) {
							close(remotefd);
							remotefd = -1;
						}
 						/* FIXME: looks ip4-specific. need to do better */
						remoteaddr.sin_family = AF_INET;
						remoteaddr.sin_addr = *(struct in_addr *) *(xgethostbyname(host)->h_addr_list);
						remoteaddr.sin_port = htons(port);
					}
					rc = 0; //it's a valid command
					break;
				case 'd': //subscriber's data, ex: "sd 0,123,10,/tmp/alert"
					{
						int add;
						char *c;
						strtoul(cmd+3, &c, 0); //discard subscriber's id (we support only 1 subscriber now)
						subscriber.pid = (pid_t)strtoul(c+1, &c, 0);
						subscriber.sig = (int)strtoul(c+1, &c, 0);
						add = (c[1] == '+');
						if (subscriber.fp)
						{
							if (add)
							{
								FILE *fp = fopen(c+2, "a");
								if (fp)
								{
									char buf[1024];
									size_t sz;
									rewind(subscriber.fp);
									do {
										sz = fread(buf, 1, sizeof(buf), subscriber.fp);
										if (sz <= 0) break;
										sz = fwrite(buf, 1, sz, fp);
									} while (sz > 0);
									fclose(fp);
								}
							}
							fclose(subscriber.fp);
						}
						subscriber.fp = add ? fopen("/tmp/.logid", "w+") : fopen((c+1), "w");
					}
					rc = 0; //it's a valid command
					break;
				}
				break;
			}
		}
		if (rc < 0)
		{
			fprintf(stderr, "Ring Log: bad command (%s)\n", msg);
		}
		return;
	}
#endif

	/* save log */
	save_log(pri, msg, msg_len);

#ifdef CONFIG_FEATURE_RLOG_FILTER
	if (test_filter(RLOG_FILTER_C, pri, msg))
	{
		//output to consle too
		int fd = device_open(_PATH_CONSOLE, O_WRONLY | O_NOCTTY | O_NONBLOCK);
		if (fd >= 0) {
			dprintf(fd, "~ %s\n", msg);
			close(fd);
		}
		else {
			fprintf(stderr, "~~%s\n", msg);
			fflush(stderr);
		}
	}
#endif

#ifdef CONFIG_FEATURE_REMOTE_LOG
#ifdef CONFIG_FEATURE_RLOG_FILTER
	if (test_filter(RLOG_FILTER_N, pri, msg)) {
#else
	if (option_mask32 & OPT_remotelog) {
#endif
		char line[MAXLINE + 1];
		/* trying connect the socket */
		if (-1 == remotefd) {
			remotefd = socket(AF_INET, SOCK_DGRAM, 0);
		}
		/* if we have a valid socket, send the message */
		if (-1 != remotefd) {
			snprintf(line, sizeof(line), "<%d>%s", pri, msg);
			/* send message to remote logger, ignore possible error */
			sendto(remotefd, line, strlen(line), 0,
					(struct sockaddr *) &remoteaddr, sizeof(remoteaddr));
		}
	}
#endif
}
