/*
 * © Copyright 1996-2012 ECMWF.
 * 
 * This software is licensed under the terms of the Apache Licence Version 2.0
 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. 
 * In applying this licence, ECMWF does not waive the privileges and immunities 
 * granted to it by virtue of its status as an intergovernmental organisation nor
 * does it submit to any jurisdiction.
 */

#include <signal.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include "mars.h"
#ifdef sgi
#include <bstring.h>
#endif

static int cnt = 0;     /* Number of kids */
static int clients = 0; /* Number of clients processed */

static void no_zombies(int sig
#if defined(__cplusplus) || defined(c_plusplus)
	,...
#endif
)
{
	int status;
	pid_t pid;


#ifdef sun_bsd
	while((pid = wait3(&status,WNOHANG,NULL)) > 0 ) {
#else
	pid = wait(&status);
#endif
	cnt--;
	marslog(LOG_DBUG,"Remaining tasks %d",cnt);


	if(WIFEXITED(status) && WEXITSTATUS(status))
	{
		marslog(LOG_WARN,"task pid %d terminated with exit %d",
		    pid,WEXITSTATUS(status));
		if(WEXITSTATUS(status) == 9)
			marslog(LOG_EXIT,"Exiting...");
	}

	if(WIFSIGNALED(status))
	{
		marslog(LOG_WARN,"task pid %d terminated by signal %d",
		    pid,WTERMSIG(status));

		/* due to s sgi bug, exit if child is dead ... */

		if(WTERMSIG(status) == SIGBUS || WTERMSIG(status) == SIGSEGV )
			marslog(LOG_EXIT,"Exiting...");
	}

	if(WIFSTOPPED(status))
		marslog(LOG_WARN,"task pid %d stopped by signal %d",
		    pid,WSTOPSIG(status));

	/* SysV need signal handler to be reinstalled */

#ifdef sun_bsd
	} /* end of while loop */
#endif
	signal(SIGCHLD,no_zombies);
}

struct sockaddr_in* addr_of(int soc)
{
	static struct sockaddr_in sin;
	marssocklen_t len = sizeof(sin);

	if(getsockname(soc,(struct sockaddr*)&sin,&len) != 0)
		marslog(LOG_EROR|LOG_PERR,"getsockname");
	return &sin;
}

int tcp_server(int port)
{

	int flg;
	int s;
	struct sockaddr_in sin;

#ifdef SO_LINGER
	struct  linger ling;
#endif

	s = socket(AF_INET, SOCK_STREAM, 0);

	if (s < 0)
	{
		marslog(LOG_EROR|LOG_PERR,"socket");
		return -1;
	}

	if(port != 0)
	{
		flg = 1 ;
		if(setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (msockopt)&flg, sizeof(flg))<0)
			marslog(LOG_WARN|LOG_PERR,"setsockopt SO_REUSEADDR");
	}

	flg = 1 ;
	if(setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, (msockopt)&flg, sizeof(flg))<0)
		marslog(LOG_WARN|LOG_PERR,"setsockopt SO_KEEPALIVE");


#if 0
#ifdef SO_REUSEPORT
	flg = 1 ;
	if(setsockopt(s, SOL_SOCKET, SO_REUSEPORT, (msockopt)&flg, sizeof(flg))<0)
		marslog(LOG_WARN|LOG_PERR,"setsockopt SO_REUSEPORT");
#endif
#endif

#ifdef SO_LINGER
	ling.l_onoff = 0;
	ling.l_linger = 0;
	if(setsockopt(s, SOL_SOCKET, SO_LINGER, (msockopt)&ling, sizeof(ling))<0)
		marslog(LOG_WARN|LOG_PERR,"setsockopt SO_LINGER");
#else
#ifdef SO_DONTLINGER
	if(setsockopt(s, SOL_SOCKET, SO_DONTLINGER, NULL, 0)<0)
		marslog(LOG_WARN|LOG_PERR,"setsockopt SO_DONTLINGER");
#endif
#endif


	bzero(&sin, sizeof(struct sockaddr_in));
	sin.sin_port        = htons(port);
	sin.sin_family      = AF_INET;
	sin.sin_addr.s_addr = INADDR_ANY;

	while (bind(s, (struct sockaddr*)&sin, sizeof(struct sockaddr_in)) == -1 )
	{
		marslog(LOG_WARN|LOG_PERR,"bind port = %d",port);
		if(port == 0) {
			close(s);
			return -1;
		}
		sleep(5);
	}

	socket_buffers(s);

	if(listen(s, 5)==-1)
	{
		close(s);
		marslog(LOG_EROR|LOG_PERR,"listen");
		return -1;
	}

	signal(SIGPIPE,SIG_IGN);

	return s;

}

const char *host_of(struct sockaddr_in* from)
{
	struct hostent  *remote;

	remote = gethostbyaddr((char*)&from->sin_addr,
	    sizeof(from->sin_addr),
	    from->sin_family);

	if(remote) 
		return remote->h_name;
	else 
		return inet_ntoa(from->sin_addr);

}

int port_of(struct sockaddr_in *from)
{
	return ntohs(from->sin_port);
}

void print_address(const char* name, struct sockaddr_in *from)
{
    if(mars.show_hosts)
    {
        if(from->sin_family == AF_INET)
        {
            char *net = inet_ntoa(from->sin_addr);
            struct hostent  *remote;

            remote = gethostbyaddr((char*)&from->sin_addr,
                sizeof(from->sin_addr),
                from->sin_family);

            if(remote)
                marslog(LOG_INFO,"%s %s (%s) %d",name,
                    remote->h_name,net,ntohs(from->sin_port));
            else
                marslog(LOG_INFO,"%s %s %d",name,net,ntohs(from->sin_port));

        }
        else marslog(LOG_INFO,"%s (connection is not from internet) %d",name,from->sin_family);
    }
}

void server_run(int port,taskproc task,void *data)
{

	struct sockaddr_in from;
	int s;
	int snew;
	marssocklen_t fromlen;
#ifdef SIG_BLOCK 
	sigset_t set;
#endif

	/* Start real server */

	s = tcp_server(port);
	if(s < 0) marslog(LOG_EXIT,"Exiting server");

	/* Dont't create zombies */

	signal(SIGCHLD,no_zombies);

	/* Dont't get killed by pipes */
	signal(SIGPIPE,SIG_IGN);

	/* Ignore hang up */
	signal(SIGHUP,SIG_IGN);

	/* loginit(stdout,stdout); */
	marslog(LOG_INFO,"Starting Server - port %d",port);

	for(;;)
	{

		fromlen = sizeof(from);
		if((snew = accept(s, (struct sockaddr*)&from, &fromlen))<0)
		{
			if(errno != EINTR)
				/* Interrupted system call : got on SIGCHLD signals */
				marslog(LOG_WARN|LOG_PERR,"accept");
		}
		else
		{
			pid_t child_pid;
			marslog(LOG_DBUG,"Got connection");

			if(from.sin_family != AF_INET)
			{
				marslog(LOG_INFO,"connection is not from internet");
				close(snew);
				continue;
			}
			else if(mars.debug)
				print_address("Got tcp connection",&from);

			fflush(0);
#ifdef SIG_BLOCK 
            sigemptyset(&set);
            sigaddset(&set, SIGCHLD);
            sigprocmask(SIG_BLOCK, &set, NULL);
#else
			sighold(SIGCHLD);
#endif

			if(mars.nofork)
			{
				task(snew,1,data);
				close(snew);
			}
			else switch(child_pid = fork())
			{
			case 0:
				close(s);
				if(mars.show_pid)
					mars.show_pid = getpid();
				task(snew,cnt+1,data);
				marsexit(0);
				break;

			case -1:
				marslog(LOG_EROR|LOG_PERR,"Cannot fork");
				close(snew);
				break;

			default:
				cnt++;
				clients++;
				close(snew);
				break;
			}

			if(mars.clients && (clients == mars.clients))
			{
				int status;
				marslog(LOG_INFO,"%d connection(s) satisfied",clients);
				marslog(LOG_INFO,"%d outstanding task(s)",cnt);
				marslog(LOG_INFO,"Waiting for child process id %d to finish",child_pid);
				waitpid(child_pid,&status,0);
				marslog(LOG_INFO,"Exiting...");
				marsexit(0);
			}

#ifdef SIG_BLOCK 
			sigprocmask(SIG_UNBLOCK, &set, NULL);  
#else
			sigrelse(SIGCHLD);
#endif

		}
	}
}

int server_mode(int *port,char *address)
{
	char    me[MAXHOSTNAMELEN];
	int     s = tcp_server(0); /* 0 means system chooses port */
	struct hostent *h;
	struct  sockaddr_in sin;
	marssocklen_t len = sizeof(sin);
	int     max = 10;
	int     cnt = 0;

	while(s<0) {
		sleep(10);
		s = tcp_server(0);
		if(cnt++ > max)
			return -1;
	}

	if(getsockname(s,(struct sockaddr*)&sin,&len)<0)
	{
		marslog(LOG_EROR|LOG_PERR,"getsockname");
		close(s);
		return -1;
	}

	*port = ntohs(sin.sin_port);

	if(gethostname(me,sizeof(me))<0)
	{
		marslog(LOG_EROR|LOG_PERR,"gethostname");
		close(s);
		return -1;
	}

	if((h = gethostbyname(me)) == NULL)
	{
		marslog(LOG_EROR|LOG_PERR,"gethostbyname");
		close(s);
		return -1;
	}

	bcopy(h->h_addr_list[0],&sin.sin_addr,h->h_length);


	strcpy(address,inet_ntoa(sin.sin_addr));

	return s;

}
