/* iocLogServer.c */
/* base/src/util $Id$ */

/*
 *	archive logMsg() from several IOC's to a common rotating file	
 *
 *
 * 	Author: 	Jeffrey O. Hill 
 *      Date:           080791 
 *
 *      Experimental Physics and Industrial Control System (EPICS)
 *
 *      Copyright 1991, the Regents of the University of California,
 *      and the University of Chicago Board of Governors.
 *
 *      This software was produced under  U.S. Government contracts:
 *      (W-7405-ENG-36) at the Los Alamos National Laboratory,
 *      and (W-31-109-ENG-38) at Argonne National Laboratory.
 *
 *      Initial development by:
 *              The Controls and Automation Group (AT-8)
 *              Ground Test Accelerator
 *              Accelerator Technology Division
 *              Los Alamos National Laboratory
 *
 *      Co-developed with
 *              The Controls and Computing Group
 *              Accelerator Systems Division
 *              Advanced Photon Source
 *              Argonne National Laboratory
 *
 *	NOTES:
 *	.01	currently runs under UNIX. could be made to run under
 *		vxWorks if NFS is used.
 *
 * Modification Log:
 * -----------------
 * .01 080791 joh	Created
 * .02 102591 joh	Dont try to reopen the log file if a write fails
 * .03 110691 joh	Disconnect if sent a zero length message
 * .04 091092 joh	Print routine messages only when in debug mode	
 * .05 091092 joh	now uses SO_REUSEADDR
 * .06 091192 joh	now uses SO_KEEPALIVE
 * .07 091192 joh	added SCCS ID
 * .08 092292 joh	improved message sent to the log
 * .09 050494 pg	HPUX port changes.
 */

static char	*pSCCSID = "@(#)iocLogServer.c	1.9\t05/05/94";

#include	<stdio.h>
#include	<string.h>
#include	<sys/types.h>
#include	<sys/time.h>
#include	<sys/socket.h>
#include	<netinet/in.h>
#include        <netdb.h>
#include 	<envDefs.h>

static long 		ioc_log_port;
static struct in_addr 	ioc_log_addr;
static long		ioc_log_file_limit;
static char		ioc_log_file_name[64];


#ifndef TRUE
#define	TRUE			1
#endif
#ifndef FALSE
#define	FALSE			0
#endif

void	acceptNewClient();
void	readFromClient();
void	logTime();
int	getConfig();
int	openLogFile();
void	handleLogFileError();
void	envFailureNotify();

struct iocLogClient {
	int			insock;
	struct ioc_log_server	*pserver;
	int			need_prefix;
	char			*ptopofstack;
	char			recvbuf[1024];
	char			name[32];
	char			ascii_time[32];
};

struct ioc_log_server {
	int		sock;
	FILE		*poutfile;
	unsigned	max_file_size;
	void   		*pfdctx;
};

extern int errno;

#ifndef ERROR
#define ERROR -1
#endif

#ifndef OK
#define OK 0
#endif


/*
 *
 *	main()
 *
 */
main()
{
	struct sockaddr_in 	serverAddr;	/* server's address */
	struct timeval          timeout;
	int			status;
	struct ioc_log_server	*pserver;
	int			optval=1;

	status = getConfig();
	if(status<0){
		printf("iocLogServer: EPICS environment underspecified\n");
		printf("iocLogServer: failed to initialize\n");
		exit(ERROR);
	}

	pserver = (struct ioc_log_server *) 
			calloc(1, sizeof *pserver);
	if(!pserver){
		abort();
	}

	pserver->pfdctx = (void *) fdmgr_init();
	if(!pserver->pfdctx){
		abort();
	}

	/*
	 * Open the socket. Use ARPA Internet address format and stream
	 * sockets. Format described in <sys/socket.h>.
	 */
	pserver->sock = socket(AF_INET, SOCK_STREAM, 0);
	if (pserver->sock<0) {
		abort();
	}
	
        status = setsockopt(    pserver->sock,
                                SOL_SOCKET,
                                SO_REUSEADDR,
                                &optval,
                                sizeof(optval));
        if(status<0){
                ca_printf(      "%s: set socket option failed\n",
                                __FILE__);
        }

	/* Zero the sock_addr structure */
	memset(&serverAddr, 0, sizeof serverAddr);
	serverAddr.sin_family = AF_INET;
	serverAddr.sin_port = htons(ioc_log_port);

	/* get server's Internet address */
	status = bind(pserver->sock, &serverAddr, sizeof serverAddr);
	if (status<0) {
		printf(	"ioc log server allready installed on port %d?\n", 
			ioc_log_port);
		exit();
	}

	/* listen and accept new connections */
	status = listen(pserver->sock, 10);
	if (status<0) {
		abort();
	}

	status = openLogFile(pserver);
	if(status<0){
		printf(	"File access problems `%s'\n", 
			ioc_log_file_name);
		exit();
	}

	status = fdmgr_add_fd(
			pserver->pfdctx, 
			pserver->sock, 
			acceptNewClient, 
			pserver);
	if(status<0){
		abort();
	}

	while(TRUE){
		timeout.tv_sec = 60; /* 1 min */
		timeout.tv_usec = 0;
		fdmgr_pend_event(pserver->pfdctx, &timeout);
		fflush(pserver->poutfile);
	}
}


/*
 *	openLogFile()
 *
 */
static int
openLogFile(pserver)
struct ioc_log_server	*pserver;
{
	if(pserver->poutfile){
		pserver->poutfile = freopen(
					ioc_log_file_name, 
					"a+", 
					pserver->poutfile);
	}else{
		pserver->max_file_size = ioc_log_file_limit; 
		pserver->poutfile = fopen(ioc_log_file_name, "a+");
	}
	if(!pserver->poutfile){
		return ERROR;
	}
	return OK;
}


/*
 *	handleLogFileError()
 *
 */
static void
handleLogFileError(pserver)
struct ioc_log_server	*pserver;
{
	int status;

	printf("iocLogServer: log file access problem (errno=%d)\n", errno);
	exit();

}
		


/*
 *	acceptNewClient()
 *
 */
static void
acceptNewClient(pserver)
struct ioc_log_server	*pserver;
{
	struct iocLogClient	*pclient;
	int			size;
	struct sockaddr_in 	addr;
	char			*pname;
	int			status;


	pclient = (struct iocLogClient *) 
			malloc(sizeof *pclient);
	if(!pclient){
		return;
	}

	pclient->insock = accept(pserver->sock, NULL, 0);
	if(pclient->insock<0){
		free(pclient);
		printf("Accept Error %d\n", errno);
		return;
	}

	pclient->pserver = pserver;
	pclient->need_prefix = TRUE;
	pclient->ptopofstack = pclient->recvbuf;

	pname = "<ukn>";
        size = sizeof addr;
	memset(&addr, 0, sizeof addr);
        status = getpeername(
                        pclient->insock,
                        &addr, 
                        &size); 
        if(status>=0){
  		struct hostent      *pent;   

    		pent = gethostbyaddr(
				&addr.sin_addr, 
				sizeof addr.sin_addr, 
				AF_INET);
		if(pent){
			pname = pent->h_name;
		}else{
			pname = (char *) inet_ntoa(&addr.sin_addr);
		}
        }

	strncpy(pclient->name, pname, sizeof(pclient->name));
	pclient->name[sizeof(pclient->name) - 1] = NULL;

	logTime(pclient);
	
	status = fprintf(
		pclient->pserver->poutfile,
		"%s %s ----- Client Connect -----\n",
		pclient->name,
		pclient->ascii_time);
	if(status<0){
		handleLogFileError();
	}

        /*
         * turn on KEEPALIVE so if the client crashes
         * this task will find out and exit
         */
	{
		long	true = true;

		status = setsockopt(
				pclient->insock,
				SOL_SOCKET,
				SO_KEEPALIVE,
				&true,
				sizeof true);
		if(status<0){
			printf("Keepalive option set failed\n");
		}
	}

	fdmgr_add_fd(
		pserver->pfdctx, 
		pclient->insock, 
		readFromClient,
		pclient);
}



/*
 * readFromClient()
 * 
 */
#define NITEMS 1

static void
readFromClient(pclient)
	struct iocLogClient *pclient;
{
	int             status;
	int             length;
	char		*pcr;
	char		*pline;
	unsigned	stacksize;

	logTime(pclient);

	stacksize = pclient->ptopofstack - pclient->recvbuf;
	length = read(pclient->insock,
		      pclient->ptopofstack,
		      sizeof(pclient->recvbuf)-stacksize-1);
	if (length <= 0) {

#		ifdef	DEBUG
		if(length == 0){
			printf("iocLogServer: nil message disconnect\n");
		}
#		endif

		/*
		 * flush any leftovers
		 */
		if(stacksize){
			status = fprintf(
				pclient->pserver->poutfile,
				"%s %s ",
				pclient->name,
				pclient->ascii_time);
			if(status<0){
				handleLogFileError();
			}
			status = fwrite(
					pclient->recvbuf,
					stacksize,
					NITEMS,
					pclient->pserver->poutfile);
			if (status != NITEMS) {
				handleLogFileError();
			}
			status = fprintf(pclient->pserver->poutfile,"\n");
			if(status<0){
				handleLogFileError();
			}
		}

		status = fprintf(
			pclient->pserver->poutfile,
			"%s %s ----- Client Disconnect -----\n",
			pclient->name,
			pclient->ascii_time);
		if(status<0){
			handleLogFileError();
		}

		fdmgr_clear_fd(
			       pclient->pserver->pfdctx,
			       pclient->insock);
		if(close(pclient->insock)<0)
			abort();
		if(free(pclient)<0)
			abort();
		return;
	}

	pclient->ptopofstack[length] = NULL;
	pline = pclient->recvbuf;
	while(TRUE){
		unsigned nchar;

		pcr = strchr(pline, '\n');

		if(pcr){
			nchar = pcr-pline+1;
		}
		else{
			nchar = strlen(pline);
			pclient->ptopofstack = pclient->recvbuf + nchar;
			memcpy(	pclient->recvbuf, 
				pline, 
				nchar);
			break;
		}

		status = fprintf(
			pclient->pserver->poutfile,
			"%s %s ",
			pclient->name,
			pclient->ascii_time);
		if(status<0){
			handleLogFileError();
		}

		status = fwrite(
				pline,
				nchar,
				NITEMS,
				pclient->pserver->poutfile);
		if (status != NITEMS) {
			handleLogFileError();
			return;
		}

		pline = pcr+1;
	}


	/*
	 * limit file length by reseting to the beginning of the file if it
	 * becomes to large
	 */
	length = ftell(pclient->pserver->poutfile);
	if (length > pclient->pserver->max_file_size) {
#		define 	FILE_BEGIN 0
#		ifdef DEBUG
			printf("ioc log server: resetting the file pointer\n");
#		endif
		fseek(pclient->pserver->poutfile, 0, FILE_BEGIN);
		status = ftruncate(
				   fileno(pclient->pserver->poutfile),
				   length);
		if (status < 0) {
			printf("truncation error %d\n", errno);
		}
	}
}




/*
 *
 *	logTime()
 *
 */
static void
logTime(pclient)
	struct iocLogClient *pclient;
{
	unsigned	sec;
	char		*pcr;

	sec = time(NULL);
	strncpy(pclient->ascii_time, 
		ctime(&sec), 
		sizeof(pclient->ascii_time) );
	pclient->ascii_time[sizeof(pclient->ascii_time)-1] = NULL;
	pcr = strchr(pclient->ascii_time, '\n');
	if(pcr){
		*pcr = NULL;
	}
}


/*
 *
 *	getConfig()
 *	Get Server Configuration
 *
 *
 */
static int
getConfig()
{
	char	inet_address_string[64];
	int	status;
	char	*pstring;

	status = envGetLongConfigParam(
			&EPICS_IOC_LOG_PORT, 
			&ioc_log_port);
	if(status<0){
		envFailureNotify(&EPICS_IOC_LOG_PORT);
		return ERROR;
	}

#ifdef FETCH_INET_ADDR
	status = envGetInetAddrConfigParam(
			&EPICS_IOC_LOG_INET, 
			&ioc_log_addr);
	if(status<0){
		envFailureNotify(&EPICS_IOC_LOG_INET);
		return ERROR;
	}
#endif

	status = envGetLongConfigParam(
			&EPICS_IOC_LOG_FILE_LIMIT, 
			&ioc_log_file_limit);
	if(status<0){
		envFailureNotify(&EPICS_IOC_LOG_FILE_LIMIT);
		return ERROR;
	}

	pstring = envGetConfigParam(
			&EPICS_IOC_LOG_FILE_NAME, 
			sizeof ioc_log_file_name,
			ioc_log_file_name);
	if(pstring == NULL){
		envFailureNotify(&EPICS_IOC_LOG_FILE_NAME);
		return ERROR;
	}
	return OK;
}



/*
 *
 *	failureNotify()
 *
 *
 */
static void
envFailureNotify(pparam)
ENV_PARAM       *pparam;
{
	printf(	"iocLogServer: EPICS environment variable `%s' undefined\n",
		pparam->name);
}
