/* minilogd.c
 * 
 * A pale imitation of syslogd. Most notably, doesn't write anything
 * anywhere except possibly back to syslogd.
 * 
 */

#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <stdarg.h>
#include <syslog.h>
#ifndef  __USE_BSD
#  define __USE_BSD
#endif
#include <unistd.h>

#include <sys/poll.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/un.h>

#define MAX_BUF_LINES 10000
#define BUF_LINE_SIZE 8192

static int we_own_log=0;
static char **buffer=NULL;
static int buflines=0;

int debug;

int recvsock;

void alarm_handler(int x) {
	alarm(0);
	close(recvsock);
	recvsock = -1;
}

void freeBuffer() {
	struct sockaddr_un addr;
	int sock;
	int x=0,conn;

	memset(&addr, 0, sizeof(addr));
	addr.sun_family = AF_LOCAL;
	strncpy(addr.sun_path,_PATH_LOG,sizeof(addr.sun_path)-1);
	/* wait for klogd to hit syslog */
	sleep(2);
	sock = socket(AF_LOCAL, SOCK_STREAM,0);
	conn=connect(sock,(struct sockaddr *) &addr,(socklen_t)sizeof(addr));
	while (x<buflines) {
		if (!conn) {
			/*printf("to syslog: %s\n", buffer[x]);*/
			write(sock,buffer[x],strlen(buffer[x])+1);
		}
		free(buffer[x]);
		x++;
	}
	free(buffer);
}

void cleanup(int exitcode) {
	/* If we own the log, unlink it before trying to free our buffer.
	 * Otherwise, sending the buffer to /dev/log doesn't make much sense.... */
	if (we_own_log) {
		perror("wol");
		unlink(_PATH_LOG);
	}
	/* Don't try to free buffer if we were called from a signal handler */
	if (exitcode<=0) {
		if (buffer)
			freeBuffer();
		exit(exitcode);
	} else
		exit(exitcode+128);
}

void runDaemon(int sock) {
	struct sockaddr_un addr;
	int x,done=0;
	ssize_t len;
	socklen_t addrlen = (socklen_t)sizeof(struct sockaddr_un);
	char *message = NULL;
	struct stat s1,s2;
	struct pollfd pfds;

	daemon(0,-1);
	/* try not to leave stale sockets lying around */
	/* Hopefully, we won't actually get any of these */
	signal(SIGHUP,cleanup);
	signal(SIGINT,cleanup);
	signal(SIGQUIT,cleanup);
	signal(SIGILL,cleanup);
	signal(SIGABRT,cleanup);
	signal(SIGFPE,cleanup);
	signal(SIGSEGV,cleanup);
	signal(SIGPIPE,cleanup);
	signal(SIGBUS,cleanup);
	signal(SIGTERM,cleanup);
	done = 0;
	/* Get stat info on /dev/log so we can later check to make sure we
	 * still own it... */
	if (stat(_PATH_LOG,&s1) != 0)
		memset(&s1, 0, sizeof(struct stat));
	while (!done) {
		pfds.fd = sock;
		pfds.events = POLLIN|POLLPRI;
		pfds.revents = 0;
		if ( ( (x=poll(&pfds,1,500))==-1) && errno !=EINTR) {
			perror("poll");
			cleanup(-1);
		}
		if ( (x>0) && (pfds.revents & (POLLIN | POLLPRI))) {
			if (message == NULL) {
				message = calloc(BUF_LINE_SIZE,sizeof(char));
			}
			recvsock = accept(sock,(struct sockaddr *) &addr, &addrlen);
			alarm(2);
			signal(SIGALRM, alarm_handler);
			len = read(recvsock,message,BUF_LINE_SIZE);
			alarm(0);
			close(recvsock);
			if (len>0) {
				/*printf("line recv'd: %s\n", message);*/
				if (buflines < MAX_BUF_LINES) {
					if (buffer)
						buffer = realloc(buffer,(buflines+1)*sizeof(char *));
					else
						buffer = malloc(sizeof(char *));
					message[strlen(message)]='\n';
					buffer[buflines]=message;
					message = NULL;
					buflines++;
				}
			}
			else {
				recvsock=-1;
			}
		}
		if ( (x>0) && ( pfds.revents & (POLLHUP | POLLNVAL)) )
			done = 1;
		/* Check to see if syslogd's yanked our socket out from under us */
		if ( (stat(_PATH_LOG,&s2)!=0) ||
				(s1.st_ino != s2.st_ino ) || (s1.st_ctime != s2.st_ctime) ||
				(s1.st_mtime != s2.st_mtime) ) { /*|| (s1.st_atime != s2.st_atime) ) {*/
			done = 1;
			we_own_log = 0;
			/*printf("someone stole our %s\n", _PATH_LOG);
			printf("st_ino:   %d %d\n", s1.st_ino, s2.st_ino);
			printf("st_ctime: %d %d\n", s1.st_ctime, s2.st_ctime);
			printf("st_atime: %d %d\n", s1.st_atime, s2.st_atime);
			printf("st_mtime: %d %d\n", s1.st_mtime, s2.st_mtime);*/
		}
	}
	free(message);
	cleanup(0);
}

int main(int argc, char **argv) {
	struct sockaddr_un addr;
	int sock;
	int pid;

	/* option processing made simple... */
	if (argc>1) debug=1;
	/* just in case */
	sock = open("/dev/null",O_RDWR);
	dup2(sock,0);
	dup2(sock,1);
	dup2(sock,2);

	memset(&addr, 0, sizeof(addr));
	addr.sun_family = AF_LOCAL;
	strncpy(addr.sun_path,_PATH_LOG,sizeof(addr.sun_path)-1);
	sock = socket(AF_LOCAL, SOCK_STREAM,0);
	unlink(_PATH_LOG);
	/* Bind socket before forking, so we know if the server started */
	if (!bind(sock,(struct sockaddr *) &addr, (socklen_t)sizeof(addr))) {
		we_own_log = 1;
		listen(sock,5);
		if ((pid=fork())==-1) {
			perror("fork");
			exit(3);
		}
		if (pid) {
			exit(0);
		} else {
			/*printf("starting daemon...\n");*/
			runDaemon(sock);
			/* shouldn't get back here... */
			exit(4);
		}
	} else {
		exit(5);
	}
}
/* vim: set ts=2 noet: */
