/*
 * © 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 "mars.h"
#include <errno.h>
#include <ctype.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <sys/ioctl.h>
#if defined(sgi) || defined(fujitsu) || defined(sun)
#include <sys/filio.h>  /* For FIONREAD */
#endif
#ifdef sgi
#include <bstring.h>
#endif

int _tcpdbg = 0;

static void pbuf(char *name,char *buf,int len)
{
	if(_tcpdbg)
	{
		int col = 0;
		printf("%s len = %d\n",name,len);
		while(len--)
		{
			if(isprint(*buf)) {
				putchar(*buf);
				if((++col % 60) == 0) putchar('\n');
			}
			buf++;
		}
		putchar('\n');
	}
}

void socket_buffers(int s)
{
#ifdef ECMWF
#ifndef hpux
	int flg = 0;
	marssocklen_t flgsize = sizeof(flg);

	/* Check Socket buffers */
	if(getsockopt(s, SOL_SOCKET, SO_SNDBUF, (msockopt)&flg, &flgsize)<0)
		marslog(LOG_WARN|LOG_PERR,"getsockopt SO_SNDBUF");
	if(flg != mars.sockbuf && mars.sockbuf)
	{
		if(setsockopt(s, SOL_SOCKET, SO_SNDBUF, (msockopt)&mars.sockbuf, sizeof(mars.sockbuf))<0)
			marslog(LOG_WARN|LOG_PERR,"setsockopt SO_SNDBUF");
		marslog(LOG_DBUG,"Changing Send socket buffers from %d to %d",flg,mars.sockbuf);
	}

	if(getsockopt(s, SOL_SOCKET, SO_RCVBUF, (msockopt)&flg, &flgsize)<0)
		marslog(LOG_WARN|LOG_PERR,"getsockopt SO_RCVBUF");
	if(flg != mars.sockbuf && mars.sockbuf)
	{
		if(setsockopt(s, SOL_SOCKET, SO_RCVBUF, (msockopt)&mars.sockbuf, sizeof(mars.sockbuf))<0)
			marslog(LOG_WARN|LOG_PERR,"setsockopt SO_RCVBUF");
		marslog(LOG_DBUG,"Changing Receive socket buffers from %d to %d",flg,mars.sockbuf);
	}
#endif
#endif
}

int writetcp(void *p,void *buf,u_int len)
{
	size_t i;
	size_t cnt;
	int   soc = *(int*)p;
	char  *b = (char*)buf;

	pbuf("writetcp",(char*)buf,len);

	for (cnt = len; cnt > 0; cnt -= i, b += i)
		switch(i = write(soc, b, cnt))
		{
		case 0:
			/* marslog(LOG_EROR,"tcp write: premature eof"); */
			return -1;
			/* break; */

		case -1:
			/* marslog(LOG_EROR|LOG_PERR,"tcp write failed"); */
			return -1;
			/* break; */
		}


	return len;
}

boolean tcp_read_ready(int soc)
{
	struct timeval timeout = { 
		0, 100, 			}; /* 1 10th sec */
	fd_set readfds;

	FD_ZERO(&readfds);
	FD_SET(soc,&readfds);

	for(;;)
		switch(select(FD_SETSIZE,
			&readfds,
			NULL,NULL,&timeout))
		{
			/* time out */
		case 0:
			return false;
			/* break; */

		case -1:
			if(errno != EINTR)
			{
				marslog(LOG_EROR|LOG_PERR,"select");
				return true;
			}
			break;

		default:
			return true;
			/* break; */
		}
}

int readtcp(void *p,void *buf,u_int len)
{
	struct timeval timeout = { 20,0}; /* 20 sec. */
	int   soc = *(int*)p;
	int   l = len;
	fd_set readfds;

	if(l == 0) return 0;

    FD_ZERO(&readfds);
	FD_SET(soc,&readfds);

	
	if(select(FD_SETSIZE,
			&readfds, NULL,NULL,&timeout) == 0)
	{
		/* after 20 sec. , send 0 bytes */
		if(write(soc,buf,0) != 0)
		{
			marslog(LOG_EROR,"tcp read: write(0) failed");
			return 0;
		}
	}
	

	switch(l =  read(soc,buf,l))
	{
	case 0:
		/* marslog(LOG_EROR,"tcp read: premature eof"); */
		return -1;
		/* break; */

	case -1:
		/* marslog(LOG_EROR|LOG_PERR,"tcp read failed"); */
		return -1;
		/* break; */

	}
	pbuf("readtcp",(char*)buf,l);

	return l;
}

boolean is_hostname(const char *host)
{
#ifdef INADDR_NONE

	in_addr_t addr;
	in_addr_t none = INADDR_NONE;

#else

#ifdef __alpha
    unsigned int  none = (unsigned int)~0;
#elif defined(fujitsu)
    u_int none = (u_int)~0;
#elif defined(__64BIT__)
	uint32_t none = (uint32_t)-1;
#else
    unsigned long none = (unsigned long)-1;
#endif

#endif

	if(inet_addr(host) != none) 
		return true;

	return (gethostbyname(host) != NULL);
}

void traceroute(struct sockaddr_in *from)
{
	char *tracecmd = getenv("MARS_TRACEROUTE");

	if(!tracecmd)
		return;
	
	if(from->sin_family == AF_INET)
	{
		char *net = inet_ntoa(from->sin_addr);
		struct hostent  *remote;
		char cmd[2048];

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

		if(remote)
		{
			sprintf(cmd,"%s %s",tracecmd,remote->h_name);
			if(system(cmd) != 0)
			{
				 marslog(LOG_EROR|LOG_PERR,"Command \"%s\" failed",cmd);
			}
		}
	}
}

int call_server(const char *host, int port, int retries)
{
	struct sockaddr_in s_in;
	struct hostent *him;
	int s;
	int status;
	int tries = 0;
	int flg;

#ifdef INADDR_NONE

	in_addr_t addr;
	in_addr_t none = INADDR_NONE;

#else

#ifdef __alpha
	unsigned int  addr;
    unsigned int  none = (unsigned int)~0;
#elif defined(fujitsu)
	u_int addr;
    u_int none = (u_int)~0;
#elif defined(__64BIT__)
	unsigned long addr;
	uint32_t none = (uint32_t)-1;
#else
	unsigned long addr;
    unsigned long none = (unsigned long)-1;
#endif

#endif

	boolean quiet = (retries <= 0);

	if(retries <= 0) retries = -retries;

	bzero(&s_in,sizeof(s_in));

	s_in.sin_port = htons(port);
	s_in.sin_family = AF_INET;

	marslog(LOG_DBUG,"Calling \"%s\" port %d",host,port);

	addr = inet_addr(host);
	s_in.sin_addr.s_addr = addr;

	if(addr == none) {
		if ((him=gethostbyname(host))==NULL)
		{
			marslog(LOG_EROR,"unknown host : %s",host);
			return -1;
		}

		s_in.sin_family = him->h_addrtype;

		bcopy(him->h_addr_list[0],&s_in.sin_addr,him->h_length);
	}

	do
	{

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

		socket_buffers(s);

		if(getenv("MARS_BIND"))
		{
			struct sockaddr_in sin;
			memset(&sin, 0, sizeof(struct sockaddr_in));
			sin.sin_port        = htons(0);
			sin.sin_family      = AF_INET;

			marslog(LOG_INFO,"Binding socket to %s",getenv("MARS_BIND"));
			sin.sin_addr.s_addr = inet_addr(getenv("MARS_BIND"));

			if(bind(s,(struct sockaddr*)&sin,sizeof(sin)) == -1) 
			{
				marslog(LOG_EROR|LOG_PERR,"bind");
				close(s);
				return -1;
			}

		}

		status = connect(s,(struct sockaddr*)&s_in,sizeof(s_in));

		if (status < 0)
		{
			if(!quiet)
				marslog(LOG_WARN|LOG_PERR,"connect : %s %d",host,port);
			socket_close(s);

			if(retries != 0 && ++tries >= retries)
			{
				if(!quiet)
					marslog(LOG_EROR,"To many retries. Giving up.");
				return -1;
			}

			sleep(5);
		}
		else
			marslog(LOG_DBUG,"Connected to address %s",
				inet_ntoa(s_in.sin_addr));

	}while(status<0);

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

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

	return (s);   /* OK */
}

static void bytes_left(int s)
{
	int nbytes=0;

	if(s == -1) 
		return;

	if(ioctl(s,FIONREAD,&nbytes) == -1)
		marslog(LOG_PERR,"Cannot determine socket status");
	else
		if(nbytes)
			marslog(LOG_WARN,"Closing connection with %d byte%s outstanding",
								nbytes,nbytes>1?"s":"");
}

int socket_close(int s)
{
	bytes_left(s);
	return close(s);
}

int socket_file_close(FILE* f)
{
	bytes_left(fileno(f));
	return fclose(f);
}
