/*
** @(#) Revision 1.3 - 05/30/97
** logmon.c
**
** =======================================================================
** Andrea Whitlock, Mobius Software Services
** whitlock@mobius-soft.com
**
** Bug reports, questions, and suggestions should be emailed to:
** mobius@mobius-soft.com
**
** This software is provided under the terms of the GNU copyleft
** (ftp://prep.ai.mit.edu/pub/gnu/COPYING-2.0), without a warranty
** of any kind.  Use at your own risk.
** =======================================================================
**
** Description:
** This a daemon that monitors a "message log" (log file in a certain format,
** see logwrite() in logwrite.c for the mlogwrite application).  It listens
** to a TCP/IP port for socket connections.  When a client establishes a 
** socket connection, the last 8K of the message log is sent to the client
** along with any new messages that are written to the message log while the
** client is running (and has a socket connection to the daemon).
**
** Notes:
** Set tabstops to 4.
**
*/

#include <stdio.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/file.h>
#include <malloc.h>
#include <errno.h>
#include <unistd.h>
#include <time.h>
#include <dirent.h>
#include <limits.h>
#include <fcntl.h>
#include "uici.h"

/* defines */

#define BLOCK_SIZE 256				/* default socket block size */
#define ALARM_TIME 150				/* alarm time (seconds) */

/* globals */

int socket_fd;						/* socket file descriptor */	
int gotsignal = 0;					/* we got a signal */
char message[2048];					/* output (logfile/stderr) message */
FILE *log;							/* log file */
static char logmonSccsId[] = "@(#)logmon.c	1.3\t05/30/97";	/* sccs info */
static char logmonVerId[] = "VER 1.3";		/* sccs info */

/* prototypes */

void catchsignal(void);
void dezombify(void);
char *timestamp(void);
void logit(char *, FILE *);
void logerror(char *, FILE *);
void main(int , char **);

/* functions */

void
catchsignal(void)
{
	sprintf(message, "caught SIGTERM or SIGINT : %s", timestamp());
	logit(message, log);
	gotsignal = 1;
}

void
dezombify(void)
{
	int status;						/* wait status */

	sprintf(message, "caught SIGCLD : %s", timestamp());
	logit(message, log);
	waitpid(-1, &status, WNOHANG);
}

void
closepipe(void)
{
	sprintf(message, "caught SIGPIPE : %s", timestamp());
	logit(message, log);

	gotsignal = 1;
}

void
keepsocketalive(void)
{
	char	delchar[2];

	if (kill(getppid(), 0) == -1) {
		sprintf(message, "caught PPID error from child %d (server dead?) : %s",
		getpid(), timestamp());
		logit(message, log);
		sprintf(message, "Server not available.\n");
		u_write(socket_fd, message, strlen(message));
		gotsignal = 1;
	} else {
		sprintf(delchar, "%c", (char)127);
		u_write(socket_fd, delchar, strlen(delchar));
	}
	alarm(ALARM_TIME);
}

char *
timestamp(void)
{
	time_t now;						/* now */
	struct tm *nowtime;				/* now */

	/* get current time */
	time(&now);
	nowtime = localtime(&now);

	/* print time */
	return asctime(nowtime);
}

void
logit(char *msg, FILE *fp)
{
	int len;						/* message length */

	len = strlen(msg);
	fwrite(msg,len,1,fp);
	fflush(fp);
}

void
logerror(char *msg, FILE *fp)
{
	char *msgtmp;					/* output (logfile/stderr) message */
	int len;						/* message length */

	perror(msg);

	len = strlen(msg);
	msgtmp = (char *)malloc(len + 2);

	if (msgtmp == NULL) {
		logit("malloc failed on msgtmp", fp);
		msgtmp = msg;
	} else {
		sprintf(msgtmp,"%s\n", msg);
	}
	logit(msgtmp, fp);

	if (msgtmp) free((void *)msgtmp);
}

void
main(int argc, char *argv[])
{
	u_short port;					/* port number */
	int port_fd;					/* port file descriptor */
	int	block_size = BLOCK_SIZE;	/* block size */
	int pid;						/* process id */
	int retval;						/* return value */
	int messagelog;					/* message log file descriptor */
	int filesize;					/* message log file size */
	char *buffptr;					/* buffer */
	char logfile[PATH_MAX];			/* log file name */
	char messfile[PATH_MAX];		/* message log file name */
	struct stat stats;				/* message log file structure */
	struct sigaction newact;		/* signal handler action */

	if (argc < 3 || argc > 4) {
		fprintf(stderr, "usage: %s port server-log message-log\n", argv[0]);
		exit(1);
	}

	port = atoi(argv[1]);
	strcpy(logfile, argv[2]);
	strcpy(messfile, argv[3]);

	/* signal handlers to catch SIGINT, SIGTERM, SIGCLD, SIGPIPE, SIGALRM */
	newact.sa_handler = (void *)catchsignal;
	sigemptyset(&newact.sa_mask);
	sigaddset(&newact.sa_mask, SIGINT);
	sigaddset(&newact.sa_mask, SIGTERM);
	sigaddset(&newact.sa_mask, SIGCLD);
	sigaddset(&newact.sa_mask, SIGPIPE);
	sigaddset(&newact.sa_mask, SIGALRM);
	newact.sa_flags = 0;
	if (sigaction(SIGINT, &newact, NULL) == -1) {
		perror("error installing signal handler for SIGINT");
		exit(1);
	}
	if (sigaction(SIGTERM, &newact, NULL) == -1) {
		perror("error installing signal handler for SIGTERM");
		exit(1);
	}
	newact.sa_handler = (void *)dezombify;
	if (sigaction(SIGCLD, &newact, NULL) == -1) {
		perror("error installing signal handler for SIGCLD");
		exit(1);
	}
	newact.sa_handler = (void *)closepipe;
	if (sigaction(SIGPIPE, &newact, NULL) == -1) {
		perror("error installing signal handler for SIGPIPE");
		exit(1);
	}
	newact.sa_handler = (void *)keepsocketalive;
	if (sigaction(SIGALRM, &newact, NULL) == -1) {
		perror("error installing signal handler for SIGALRM");
		exit(1);
	}

	/* open log file */
	if ((log = fopen(logfile,"a")) == NULL) {
		perror("error opening logfile");
		exit(1);
	}

	logit("\n---------------------------------------------------------------\n",
		log);
	sprintf(message,"%s started: %s\n", argv[0], timestamp());
	logit(message, log);

	buffptr = (char *)malloc(block_size + 1);
	if (buffptr == NULL) {
		logerror("error mallocing buffptr", log);
		exit(1);
	}

	/* bind to port */
	if ((port_fd = u_open(port)) == -1) {
		u_error("error binding to socket");
		logerror("error binding to socket", log);
		exit(1);
	}

	sprintf(message, "binding to port %d on port_fd %d\n", port, port_fd);
	logit(message, log);
	logit(message, stderr);

	/* make myself a daemon */
	fprintf(stderr,"starting daemon\n");
	if (fork()) exit(0);
	sprintf(message, "daemon started: pid %d\n", getpid());
	logit(message, log);

	/* listen to the port */
	while (!gotsignal) {

		socket_fd = u_listen(port_fd);
		if (socket_fd == -1) continue;

		/* fork a child to monitor the log and write to the socket */
		if ((pid = fork()) == 0) {

			sprintf(message, "child %d listening to socket_fd %d : %s", 
				getpid(), socket_fd, timestamp());
			logit(message, log);

			/* set up socket "keep alive" alarm so we notice SIGPIPEs */
			alarm(ALARM_TIME);

			/* open the message log file */
			if ((messagelog = open(messfile, O_RDONLY)) == -1) {
				sprintf(message, "error opening message log %s : %s", 
					messfile, timestamp());
				logit(message, log);
			} else {
				if (lseek(messagelog, -8196L, SEEK_END) > 0) {
					strcpy(buffptr, "a");
					while(strcmp(buffptr, "\n") != 0 &&
						read(messagelog, buffptr, 1)) ;
				}
				retval = 0;
				filesize = 0;
				while (!gotsignal && retval >= 0) {
					if (fstat(messagelog, &stats) < 0) {
						retval = -1;
						continue;
					}
					if (stats.st_size == filesize && !retval) {
						sleep(2);
						continue;
					} else {
						/* debug 
						sprintf(message, "Filesize change (%d != %d) : %s", 
							filesize, stats.st_size, timestamp());
						logit(message, log);
						*/
						filesize = stats.st_size;
					}
					retval = read(messagelog, buffptr, block_size);
					/* debug 
					sprintf(message, "Read %d bytes : %s", retval, timestamp());
					logit(message, log);
					*/
					buffptr[retval] = '\0';
					if (u_write(socket_fd, buffptr, strlen(buffptr)) < 0) {
						if (socket_fd > 0) {
							u_close(socket_fd);
							sprintf(message, 
								"closing socket_fd %d for child %d : %s", 
								socket_fd, getpid(), timestamp());
							logit(message, log);
							socket_fd = -1;
						}
					}
				}
				close(messagelog);
			}
			if (socket_fd > 0) {
				u_close(socket_fd);
				sprintf(message, "closing socket_fd %d for child %d : %s", 
					socket_fd, getpid(), timestamp());
				logit(message, log);
				socket_fd = 1;
			}
			exit(0);
		}
		if (socket_fd > 0) {
			u_close(socket_fd);
			sprintf(message, "closing socket_fd %d to server : %s", 
				socket_fd, timestamp());
			logit(message, log);
			socket_fd = 1;
		}
	}

	/* log signal caught */
	if (gotsignal) logit("\nsignal received\n", log);

	/* close the socket */
	if (socket_fd != -1) {
		logit("closing socket\n", log);
		u_close(socket_fd);
	}

	/* close the port */
	logit("closing port\n", log);
	u_close(port_fd);

	/* free buffers */
	free((void *)buffptr);

	sprintf(message, "\n%s shutdown: %s", argv[0], timestamp());
	logit(message, log);

	/* close files */
	fflush(log);
	fclose(log);

	exit(0);
}
