/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/* Portions Copyright 2010 by Rainer Gerhards and Adiscon
 */
/*
 * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 *	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T
 *	All Rights Reserved
 */

/*
 * University Copyright- Copyright (c) 1982, 1986, 1988
 * The Regents of the University of California
 * All Rights Reserved
 *
 * University Acknowledgment- Portions of this document are derived from
 * software developed by the University of California, Berkeley, and its
 * contributors.
 */
#include "config.h"
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
#include <sys/poll.h>
#include <pthread.h>
#include <fcntl.h>
#include <stropts.h>
#include <assert.h>
#include <sys/strlog.h>

#include "rsyslog.h"
#include "imklog.h"

/* TODO: this define should be changed over time to the more generic
 * system-provided (configurable) upper limit. However, it is quite
 * unexpected that Solaris-emitted messages are so long, so it seems
 * acceptable to set a fixed (relatively high) limit for the time
 * being -- and gain some experience with it. -- rgerhars, 2010-04-12
 */
#define MAXLINE 4096

static struct pollfd Pfd;		/* Pollfd for local the log device */


/* findnl_bkwd:
 *	Scans each character in buf until it finds the last newline in buf,
 *	or the scanned character becomes the last COMPLETE character in buf.
 *	Returns the number of scanned bytes.
 *
 *	buf - pointer to a buffer containing the message string
 *	len - the length of the buffer
 */
size_t
findnl_bkwd(const char *buf, const size_t len)
{
	const char *p;
	size_t	mb_cur_max;

	if (len == 0) {
		return (0);
	}

	mb_cur_max = MB_CUR_MAX;

	if (mb_cur_max == 1) {
		/* single-byte locale */
		for (p = buf + len - 1; p != buf; p--) {
			if (*p == '\n') {
				return ((size_t)(p - buf));
			}
		}
		return ((size_t)len);
	} else {
		/* multi-byte locale */
		int mlen;
		const char *nl;
		size_t	rem;

		p = buf;
		nl = NULL;
		for (rem = len; rem >= mb_cur_max; ) {
			mlen = mblen(p, mb_cur_max);
			if (mlen == -1) {
				/*
				 * Invalid character found.
				 */
				dbgprintf("klog:findnl_bkwd: Invalid MB sequence\n");
				/*
				 * handle as a single byte character.
				 */
				p++;
				rem--;
			} else {
				/*
				 * It's guaranteed that *p points to
				 * the 1st byte of a multibyte character.
				 */
				if (*p == '\n') {
					nl = p;
				}
				p += mlen;
				rem -= mlen;
			}
		}
		if (nl) {
			return ((size_t)(nl - buf));
		}
		/*
		 * no newline nor null byte found.
		 * Also it's guaranteed that *p points to
		 * the 1st byte of a (multibyte) character
		 * at this point.
		 */
		return (len - rem);
	}
}
//___ end


/* Attempts to open the local log device
 * and return a file descriptor.
 */
int
sun_openklog(char *name, int mode)
{
	int fd;
	struct strioctl str;

	if ((fd = open(name, mode)) < 0) {
		dbgprintf("klog:openklog: cannot open %s (%d)\n",
		    name, errno);
		return (-1);
	}
	str.ic_cmd = I_CONSLOG;
	str.ic_timout = 0;
	str.ic_len = 0;
	str.ic_dp = NULL;
	if (ioctl(fd, I_STR, &str) < 0) {
		dbgprintf("klog:openklog: cannot register to log "
		    "console messages (%d)\n", errno);
		return (-1);
	}
	Pfd.fd = fd;
	Pfd.events = POLLIN;
	return (fd);
}


/*
 * Pull up one message from log driver.
 */
void
sun_getkmsg()
{
	int flags = 0, i;
	char *lastline;
	struct strbuf ctl, dat;
	struct log_ctl hdr;
	char buf[MAXLINE+1];
	size_t buflen;
	size_t len;
	char tmpbuf[MAXLINE+1];

	dat.maxlen = MAXLINE;
	dat.buf = buf;
	ctl.maxlen = sizeof (struct log_ctl);
	ctl.buf = (caddr_t)&hdr;

	while ((i = getmsg(Pfd.fd, &ctl, &dat, &flags)) == MOREDATA) {
		lastline = &dat.buf[dat.len];
		*lastline = '\0';

		dbgprintf("klog:sys_poll: getmsg: dat.len = %d\n", dat.len);
		buflen = strlen(buf);
		len = findnl_bkwd(buf, buflen);

		(void) memcpy(tmpbuf, buf, len);
		tmpbuf[len] = '\0';

		Syslog(LOG_INFO, (uchar*) buf);

		if (len != buflen) {
			/* If anything remains in buf */
			size_t remlen;

			if (buf[len] == '\n') {
				/* skip newline */
				len++;
			}

			/* Move the remaining bytes to
			 * the beginnning of buf.
			 */

			remlen = buflen - len;
			(void) memmove(buf, &buf[len], remlen);
			dat.maxlen = MAXLINE - remlen;
			dat.buf = &buf[remlen];
		} else {
			dat.maxlen = MAXLINE;
			dat.buf = buf;
		}
	}

	if (i == 0 && dat.len > 0) {
		dat.buf[dat.len] = '\0';
		/* Format sys will enqueue the log message.
		 * Set the sync flag if timeout != 0, which
		 * means that we're done handling all the
		 * initial messages ready during startup.
		 */
		dbgprintf("klog:getkmsg: getmsg: dat.maxlen = %d\n", dat.maxlen);
		dbgprintf("klog:getkmsg: getmsg: dat.len = %d\n", dat.len);
		dbgprintf("klog:getkmsg: getmsg: strlen(dat.buf) = %d\n", strlen(dat.buf));
		dbgprintf("klog:getkmsg: getmsg: dat.buf = \"%s\"\n", dat.buf);
		dbgprintf("klog:getkmsg: buf len = %d\n", strlen(buf));
		Syslog(LOG_INFO, (uchar*) buf);
	} else if (i < 0 && errno != EINTR) {
		if(1){ /* V5-TODO: rsyslog-like termination! (!shutting_down) { */
			dbgprintf("klog:kernel log driver read error");
		}
		// TODO trigger retry logic
		//(void) close(Pfd.fd);
		//Pfd.fd = -1;
	}
}


/* this thread listens to the local stream log driver for log messages
 * generated by this host, formats them, and queues them to the logger
 * thread.
 */
/*ARGSUSED*/
void *
sun_sys_poll()
{
	int nfds;

	dbgprintf("klog:sys_poll: sys_thread started\n");

	for (;;) {
		errno = 0;

		nfds = poll(&Pfd, 1, INFTIM);

		if (nfds == 0)
			continue;

		if (nfds < 0) {
			if (errno != EINTR)
				dbgprintf("klog:poll error");
			continue;
		}
		if (Pfd.revents & POLLIN) {
			sun_getkmsg();
		} else {
			/* TODO: shutdown, the rsyslog way (in v5!) -- check shutdown flag */
			if (Pfd.revents & (POLLNVAL|POLLHUP|POLLERR)) {
			// TODO: trigger retry logic
/*				logerror("kernel log driver poll error");
				(void) close(Pfd.fd);
				Pfd.fd = -1;
				*/
			}
		}

	}
	/*NOTREACHED*/
	return (NULL);
}
