/*
 * Read log messages from the kernel - send them to syslogd
 *
 * Steve Lord (lord@cray.com) 7th Nov 92
 *
 * Modified to check for kernel info by Dr. G.W. Wettstein 02/17/93.
 *
 * Fri Mar 12 16:53:56 CST 1993:  Dr. Wettstein
 * 	Modified LogLine to use a newline as the line separator in
 *	the kernel message buffer.
 *
 *	Added debugging code to dump the contents of the kernel message
 *	buffer at the start of the LogLine function.
 *
 * Thu Jul 29 11:40:32 CDT 1993:  Dr. Wettstein
 *	Added syscalls to turn off logging of kernel messages to the
 *	console when klogd becomes responsible for kernel messages.
 *
 *	klogd now catches SIGTERM and SIGKILL signals.  Receipt of these
 *	signals cases the clean_up function to be called which shuts down
 *	kernel logging and re-enables logging of messages to the console.
 *
 * Sat Dec 11 11:54:22 CST 1993:  Dr. Wettstein
 *	Added fixes to allow compilation with no complaints with -Wall.
 *
 *      When the daemon catches a fatal signal (SIGTERM, SIGKILL) a 
 *	message is output to the logfile advising that the daemon is
 *	going to terminate.
 *
 * Thu Jan  6 11:54:10 CST 1994:  Dr. Wettstein
 *	Major re-write/re-organization of the code.
 *
 *	Klogd now assigns kernel messages to priority levels when output
 *	to the syslog facility is requested.  The priority level is
 *	determined by decoding the prioritization sequence which is
 *	tagged onto the start of the kernel messages.
 *
 *	Added the following program options: -f arg -c arg -s -o -d
 *
 *		The -f switch can be used to specify that output should
 *		be written to the named file.
 *
 *		The -c switch is used to specify the level of kernel
 *		messages which are to be directed to the console.
 *
 *		The -s switch causes the program to use the syscall
 *		interface to the kernel message facility.  This can be
 *		used to override the presence of the /proc filesystem.
 *
 *		The -o switch causes the program to operate in 'one-shot'
 *		mode.  A single call will be made to read the complete
 *		kernel buffer.  The contents of the buffer will be
 *		output and the program will terminate.
 *
 *		The -d switch causes 'debug' mode to be activated.  This
 *		will cause the daemon to generate LOTS of output to stderr.
 *
 *	The buffer decomposition function (LogLine) was re-written to
 *	squash a bug which was causing only partial kernel messages to
 *	be written to the syslog facility.
 *
 *	The signal handling code was modified to properly differentiate
 *	between the STOP and TSTP signals.
 *
 *	Added pid saving when the daemon detaches into the background.  Thank
 *	you to Juha Virtanen (jiivee@hut.fi) for providing this patch.
 *
 */


#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <syslog.h>
#include <errno.h>
#include <sys/fcntl.h>
#include <sys/stat.h>
#include <linux/time.h>
#include <stdarg.h>

#define __LIBRARY__
#include <linux/unistd.h>
#define __NR_sys_syslog __NR_syslog
_syscall3(int,sys_syslog,int, type, char *, buf, int, len);

#define LOG_BUFFER_SIZE 4096
#define LOG_LINE_LENGTH 1024
#define PROC_KMSG "/proc/kmsg"

static char	*PidFile = "/etc/klogd.pid";

static int	kmsg,
		caught_TSTP = 0,
		console_log_level = 1;

static int	use_syscall = 0,
		one_shot = 0,
		debugging = 0;

static char log_buffer[LOG_BUFFER_SIZE];

static FILE *output_file = (FILE *) 0;

static enum LOGSRC {none, proc, kernel} logsrc;

static enum LOGSRC GetKernelLogSrc(void);


/* Function prototypes. */
void CloseLogSrc(void);
static enum LOGSRC GetKernelLogSrc(void);
static void Syslog(int, char *, ...);
static void LogLine(char *ptr, int len);
static void LogKernelLine(void);
static void LogProcLine(void);
int main(int argc, char *argv[]);
int sys_syslog(int type, char *buf, int len);


void CloseLogSrc()

{
	/* Turn on logging of messages to console. */
  	sys_syslog(7, NULL, 0);
  
        /* Shutdown the log sources. */
	switch ( logsrc )
	{
	    case kernel:
		sys_syslog(0, 0, 0);
		Syslog(LOG_INFO, "Kernel logging (sys_syslog) stopped.");
		break;
            case proc:
		close(kmsg);
		Syslog(LOG_INFO, "Kernel logging (proc) stopped.");
		break;
	    case none:
		break;
	}

	if ( output_file != (FILE *) 0 )
		fflush(output_file);
	return;
}


void restart()

{
	signal(SIGCONT, restart);
	if ( caught_TSTP != 1 )
	{
		Syslog(LOG_INFO, "Kernel logging re-started after SIGSTOP.");
		return;
	}
	
	CloseLogSrc();
	logsrc = GetKernelLogSrc();
	caught_TSTP = 0;
	return;
}


void stop_logging()
{
	signal(SIGTSTP, stop_logging);
	CloseLogSrc();
	caught_TSTP = 1;
	logsrc = none;
	return;
}


void stop_daemon(int sig)

{
  	CloseLogSrc();
 	Syslog(LOG_INFO, "Kernel log daemon terminating.");
 	sleep(1);
	if ( output_file != (FILE *) 0 )
		fclose(output_file);
	closelog();
	exit(1);
}


static enum LOGSRC GetKernelLogSrc(void)

{
	auto struct stat sb;


	/* Set level of kernel console messaging.. */
	if ( (sys_syslog(8, NULL, console_log_level) < 0) && \
	     (errno == EINVAL) )
	{
		/*
		 * An invalid arguement error probably indicates that
		 * a pre-0.14 kernel is being run.  At this point we
		 * issue an error message and simply shut-off console
		 * logging completely.
		 */
		Syslog(LOG_WARNING, "Cannot set console log level - disabling "
			      "console output.");
		sys_syslog(6, NULL, 0);
	}
	

	/*
	 * First do a stat to determine whether or not the proc based
	 * file system is available to get kernel messages from.
	 */
	if ( use_syscall || ((stat(PROC_KMSG, &sb) < 0) && (errno == ENOENT)) )
	{
	  	/* Initialize kernel logging. */
	  	sys_syslog(1, NULL, 0);
		Syslog(LOG_INFO, "Kernel logging (sys_syslog) started.");
		return(kernel);
	}
	
	if ( (kmsg = open(PROC_KMSG, O_RDONLY)) < 0 )
	{
		fputs("klogd: Cannot open proc file system.", stderr);
		sys_syslog(7, NULL, 0);
		exit(1);
	}

	Syslog(LOG_INFO, "Kernel logging (proc) started.");
	return(proc);
}


static void Syslog(int priority, char *fmt, ...)

{
	va_list ap;

	if ( debugging )
	{
		fputs("Logging line:\n", stderr);
		fprintf(stderr, "\tLine: %s\n", fmt);
		fprintf(stderr, "\tPriority: %c\n", *(fmt+1));
	}

	/* Handle output to a file. */
	if ( output_file != (FILE *) 0 )
	{
		va_start(ap, fmt);
		vfprintf(output_file, fmt, ap);
		va_end(ap);
		fputc('\n', output_file);
		fflush(output_file);
		return;
	}
	
	/* Output using syslog. */
	if ( *fmt == '<' )
	{
		switch ( *(fmt+1) )
		{
		    case '0':
			priority = LOG_EMERG;
			break;
		    case '1':
			priority = LOG_ALERT;
			break;
		    case '2':
			priority = LOG_CRIT;
			break;
		    case '3':
			priority = LOG_ERR;
			break;
		    case '4':
			priority = LOG_WARNING;
			break;
		    case '5':
			priority = LOG_NOTICE;
			break;
		    case '6':
			priority = LOG_INFO;
			break;
		    case '7':
		    default:
			priority = LOG_DEBUG;
		}
		fmt += 3;
	}
	
	va_start(ap, fmt);
	vsyslog(priority, fmt, ap);
	va_end(ap);

	return;
}

	
static void LogLine(char *ptr, int len)

{
	auto int idx = 0;
	static int index = 0;
	auto char *nl;
	static char line[LOG_LINE_LENGTH];

	if ( debugging && (len != 0) )
	{
		fprintf(stderr, "Log buffer contains: %d characters.\n", len);
		fprintf(stderr, "Line buffer contains: %d characters.\n", \
			index);
		while ( idx <= len )
		{
			fprintf(stderr, "Character #%d - %d:%c\n", idx, \
				ptr[idx], ptr[idx]);
			++idx;
		}
		if ( index != 0 )
		{
			fputs("Line buffer contains an unterminated line:\n", \
			      stderr);
			fprintf(stderr, "\tCount: %d\n", index);
			fprintf(stderr, "%s\n\n", line);
		}
	}

	if ( index == 0 )
		memset(line, '\0', sizeof(line));
	
	while (len) {
		nl = strpbrk(ptr, "\r\n"); /* Find first line terminator */
		if (nl) {
			len -= nl - ptr + 1;
			strncat(line, ptr, nl - ptr);
			ptr = nl + 1;
			/* Check for empty log line (may be produced if 
			   kernel messages have multiple terminators, eg.
			   \n\r) */
			if ( (*line != '\n') && (*line != '\r') )
				Syslog(LOG_INFO, line);
		      index = 0;
		      memset(line, '\0', sizeof(line));
		 }
		 else{
			 if ( debugging )
			 {
				 fputs("No terminator - leftover:\n", stderr);
				 fprintf(stderr, "\tCharacters: %d\n", len);
				 fprintf(stderr, "\tIndex: %d\n", index);
				 fputs("\tLine: ", stderr);
				 fprintf(stderr, "%s\n", line);
			 }
			 
			strncat(line, ptr, len);
			index += len;
			len = 0;
		}
	}

	return;
}


static void LogKernelLine(void)

{
	auto int len;

	if ( (len = sys_syslog(2, log_buffer, sizeof(log_buffer))) < 0 )
	{
		if ( errno == EINTR )
			return;
		fprintf(stderr, "Error return from sys_sycall: %d - %s\n", \
			errno, strerror(errno));
	}
	
	LogLine(log_buffer, len);
	return;
}


static void LogProcLine(void)

{
	auto int rdcnt;

	if ( (rdcnt = read(kmsg, log_buffer, sizeof(log_buffer))) < 0 )
	{
		if ( errno == EINTR )
			return;
		Syslog(LOG_ERR, "Cannot read proc file system.");
	}
	
	LogLine(log_buffer, rdcnt);

	return;
}


int main(argc, argv)

	int argc;

	char *argv[];

{
	auto int ch;

	auto char *log_level = (char *) 0,
		  *output = (char *) 0;

	auto FILE *fp;

	/* Parse the command-line. */
	while ((ch = getopt(argc, argv, "c:df:os")) != EOF)
		switch((char)ch)
		{
		    case 'c':		/* Set console message level. */
			log_level = optarg;
			break;
		    case 'd':		/* Activity debug mode. */
			debugging = 1;
			break;
		    case 'f':		/* Define an output file. */
			output = optarg;
			break;
		    case 'o':		/* One-shot mode. */
			one_shot = 1;
			break;
		    case 's':		/* Use syscall interface. */
			use_syscall = 1;
			break;
		}


	/* Set console logging level. */
	if ( log_level != (char *) 0 )
	{
		if ( (strlen(log_level) > 1) || \
		     (strchr("1234567", *log_level) == (char *) 0) )
		{
			fprintf(stderr, "klogd: Invalid console logging "
				"level <%s> specified.\n", log_level);
			return(1);
		}
		console_log_level = *log_level - '0';
	}		


	/*
	 * The following code allows klogd to auto-background itself.
	 * What happens is that the program forks and the parent quits.
	 * The child closes all its open file descriptors, and issues a
	 * call to setsid to establish itself as an independent session
	 * immune from control signals.
	 */
#if !defined(NO_FORK)
	if ( !one_shot )
	{
		if ( fork() == 0 )
		{
			auto int fl;
		
			/* This is the child closing its file descriptors. */
			for (fl= 0; fl <= FD_SETSIZE; ++fl)
				close(fl);
			setsid();
		}
		else
			exit(0);
	}
#endif


	/* Signal setups. */
	for (ch= 1; ch < NSIG; ++ch)
		signal(ch, SIG_IGN);
	signal(SIGINT, stop_daemon);
	signal(SIGKILL, stop_daemon);
	signal(SIGTERM, stop_daemon);
	signal(SIGHUP, stop_daemon);
	signal(SIGTSTP, stop_logging);
	signal(SIGCONT, restart);


	/* Open outputs. */
	if ( output != (char *) 0 )
	{
		if ( (output_file = fopen(output, "w")) == (FILE *) 0 )
		{
			fprintf(stderr, "klogd: Cannot open output file %s - "\
				"%s\n", output, strerror(errno));
			return(1);
		}
	}
	else
		openlog("kernel", 0, LOG_KERN);


	/* Handle one-shot logging. */
	if ( one_shot )
	{
		if ( (logsrc = GetKernelLogSrc()) == kernel )
			LogKernelLine();
		else
			LogProcLine();
		stop_daemon(0);
	}

	/* tuck my process id away */
	fp = fopen(PidFile, "w");
	if (fp != NULL) {
		fprintf(fp, "%d\n", getpid());
		(void) fclose(fp);
	}

	/* Determine where kernel logging information is to come from. */
	sleep(1);
	logsrc = GetKernelLogSrc();

        /* The main loop. */
	while (1)
		switch ( logsrc )
		{
			case kernel:
	  			LogKernelLine();
				break;
			case proc:
				LogProcLine();
				break;
		        case none:
				break;
		}
}
