/*--------------------------------------------------------------------
 * FILE:
 *     pool_connection_pool.c
 *
 * NOTE:
 *     connection pool stuff
 *
 * Portions Copyright (c) 2003-2006, Atsushi Mitani
 * Portions Copyright (c) 2003-2006, Tatsuo Ishii
 *--------------------------------------------------------------------
 */
/*
 * Permission to use, copy, modify, and distribute this software and
 * its documentation for any purpose and without fee is hereby
 * granted, provided that the above copyright notice appear in all
 * copies and that both that copyright notice and this permission
 * notice appear in supporting documentation, and that the name of the
 * author not be used in advertising or publicity pertaining to
 * distribution of the software without specific, written prior
 * permission. The author makes no representations about the
 * suitability of this software for any purpose.  It is provided "as
 * is" without express or implied warranty.
 *
 */
#include "postgres.h"
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/un.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <signal.h>
#include <string.h>
#include <unistd.h>
#include <time.h>

#ifdef HAVE_NETINET_TCP_H
#include <netinet/tcp.h>
#endif

#include "replicate_com.h"
#include "pglb.h"

POOL_CONNECTION_POOL *pool_connection_pool;	/* connection pool */

int pool_init_cp(void);
POOL_CONNECTION_POOL *pool_get_cp(char *user, char *database, int protoMajor);
void pool_discard_cp(char *user, char *database, int protoMajor);
POOL_CONNECTION_POOL *pool_create_cp(void);
void pool_connection_pool_timer(POOL_CONNECTION_POOL *backend);
void pool_backend_timer_handler(int sig);
int connect_inet_domain_socket(int secondary_backend);
int connect_unix_domain_socket(int secondary_backend);
char PGRis_same_host(char * host1, char * host2);
void pool_finish(void);


static POOL_CONNECTION_POOL_SLOT *create_cp(POOL_CONNECTION_POOL_SLOT *cp, int secondary_backend);
static POOL_CONNECTION_POOL *new_connection(POOL_CONNECTION_POOL *p);



/*
* initialize connection pools. this should be called once at the startup.
*/
int pool_init_cp(void)
{
	char * func = "pool_init_cp()";
	pool_connection_pool = (POOL_CONNECTION_POOL *)malloc(sizeof(POOL_CONNECTION_POOL)*Max_Pool);
	if (pool_connection_pool == NULL)
	{
		show_error("%s: malloc() failed[%s]",func,strerror(errno));
		return -1;
	}
	memset(pool_connection_pool, 0, sizeof(POOL_CONNECTION_POOL)*Max_Pool);

	return 0;
}

/*
* find connection by user and database
*/
POOL_CONNECTION_POOL *pool_get_cp(char *user, char *database, int protoMajor)
{
	char * func = "pool_get_cp()";
	int i;

	POOL_CONNECTION_POOL *p = pool_connection_pool;

	if (p == NULL)
	{
		show_error("%s: pool_connection_pool is not initialized",func);
		return NULL;
	}

	for (i=0;i<Max_Pool;i++)
	{
		if (MASTER_CONNECTION(p) &&
			MASTER_CONNECTION(p)->sp->major == protoMajor &&
			MASTER_CONNECTION(p)->sp->user != NULL &&
			strcmp(MASTER_CONNECTION(p)->sp->user, user) == 0 &&
			strcmp(MASTER_CONNECTION(p)->sp->database, database) == 0)
		{
			/* mark this connection is under use */
			MASTER_CONNECTION(p)->closetime = 0;
			return p;
		}
		p++;
	}
	return NULL;
}

/*
 * disconnect and release a connection to the database
 */
void pool_discard_cp(char *user, char *database, int protoMajor)
{
	char * func = "pool_discard_cp()";
	POOL_CONNECTION_POOL *p = pool_get_cp(user, database, protoMajor);

	if (p == NULL)
	{
		show_error("%s: cannot get connection pool for user %s datbase %s", func,user, database);
		return;
	}

	free(MASTER_CONNECTION(p)->sp->user);
	free(MASTER_CONNECTION(p)->sp->database);
	free(MASTER_CONNECTION(p)->sp->startup_packet);
	pool_close(MASTER_CONNECTION(p)->con);

	memset(p, 0, sizeof(POOL_CONNECTION_POOL));
}


/*
* create a connection pool by user and database
*/
POOL_CONNECTION_POOL *pool_create_cp(void)
{
	char * func = "pool_create_cp()";
	int i;
	time_t closetime;
	POOL_CONNECTION_POOL *oldestp;

	POOL_CONNECTION_POOL *p = pool_connection_pool;

	if (p == NULL)
	{
		show_error("%s: pool_connection_pool is not initialized",func);
		return NULL;
	}

	for (i=0; i<Max_Pool; i++)
	{
		if (MASTER_CONNECTION(p) == NULL)
			return new_connection(p);
		p++;
	}

#ifdef PRINT_DEBUG
	show_debug("%s:no empty connection slot was found",func);
#endif			

	/*
	 * no empty connection slot was found. look for the oldest connection and discard it.
	 */
	oldestp = p = pool_connection_pool;
	closetime = MASTER_CONNECTION(p)->closetime;
	for (i=0; i<Max_Pool; i++)
	{
#ifdef PRINT_DEBUG
		show_debug("%s:user: %s database: %s closetime: %d",
				func,
				   MASTER_CONNECTION(p)->sp->user,
				   MASTER_CONNECTION(p)->sp->database,
				   MASTER_CONNECTION(p)->closetime);
#endif			
		if (MASTER_CONNECTION(p)->closetime < closetime)
		{
			closetime = MASTER_CONNECTION(p)->closetime;
			oldestp = p;
		}
		p++;
	}

	p = oldestp;
	pool_send_frontend_exits(p);

#ifdef PRINT_DEBUG
	show_debug("%s:discarding old %d th connection. user: %s database: %s", 
			   func,
			   oldestp - pool_connection_pool,
			   MASTER_CONNECTION(p)->sp->user,
			   MASTER_CONNECTION(p)->sp->database);
#endif			

	free(MASTER_CONNECTION(p)->sp->user);
	free(MASTER_CONNECTION(p)->sp->database);
	free(MASTER_CONNECTION(p)->sp->startup_packet);
	pool_close(MASTER_CONNECTION(p)->con);

	memset(p, 0, sizeof(POOL_CONNECTION_POOL));

	return new_connection(p);
}

/*
 * set backend connection close timer
 */
void pool_connection_pool_timer(POOL_CONNECTION_POOL *backend)
{
#ifdef PRINT_DEBUG
	char * func = "pool_connection_pool_timer()";
#endif			
	POOL_CONNECTION_POOL *p = pool_connection_pool;
	int i;

#ifdef PRINT_DEBUG
	show_debug("%s:pool_connection_pool_timer: called",func);
#endif			

	MASTER_CONNECTION(backend)->closetime = time(NULL);		/* set connection close time */

	if (Connection_Life_Time == 0)
		return;

	/* look for any other timeout */
	for (i=0;i<Max_Pool;i++, p++)
	{
		if (!MASTER_CONNECTION(p))
			continue;
		if (MASTER_CONNECTION(p)->sp->user == NULL)
			continue;

		if (p != backend && MASTER_CONNECTION(p)->closetime)
			return;
	}

	/* no other timer found. set my timer */
#ifdef PRINT_DEBUG
	show_debug("%s: set alarm after %d seconds",func, Connection_Life_Time);
#endif			
	signal(SIGALRM, pool_backend_timer_handler);
	alarm(Connection_Life_Time);
}

/*
 * backend connection close timer handler
 */
void pool_backend_timer_handler(int sig)
{
#define TMINTMAX 0x7fffffff

#ifdef PRINT_DEBUG
	char * func = "pool_backend_timer_handler()";
#endif			
	POOL_CONNECTION_POOL *p = pool_connection_pool;
	int i;
	time_t now;
	time_t nearest = TMINTMAX;

	now = time(NULL);

#ifdef PRINT_DEBUG
	show_debug("%s:called at %d", func,now);
#endif			

	for (i=0;i<Max_Pool;i++, p++)
	{
		if (!MASTER_CONNECTION(p))
			continue;
		if (MASTER_CONNECTION(p)->sp->user == NULL)
			continue;

		/* timer expire? */
		if (MASTER_CONNECTION(p)->closetime)
		{
#ifdef PRINT_DEBUG
			show_debug("%s: expire time: %d",
					   func,
					   MASTER_CONNECTION(p)->closetime+Connection_Life_Time);
#endif			

			if (now >= (MASTER_CONNECTION(p)->closetime+Connection_Life_Time))
			{
				/* discard expired connection */
#ifdef PRINT_DEBUG
				show_debug("%s: expires user %s database %s", func, MASTER_CONNECTION(p)->sp->user, MASTER_CONNECTION(p)->sp->database);
#endif			

				pool_send_frontend_exits(p);

				free(MASTER_CONNECTION(p)->sp->user);
				free(MASTER_CONNECTION(p)->sp->database);
				free(MASTER_CONNECTION(p)->sp->startup_packet);
				pool_close(MASTER_CONNECTION(p)->con);

				memset(p, 0, sizeof(POOL_CONNECTION_POOL));
			}
			else
			{
				/* look for nearest timer */
				if (MASTER_CONNECTION(p)->closetime < nearest)
					nearest = MASTER_CONNECTION(p)->closetime;
			}
		}
	}

	/* any remaining timer */
	if (nearest != TMINTMAX)
	{
		nearest = Connection_Life_Time - (now - nearest);
		if (nearest <= 0)
		  nearest = 1;
		signal(SIGALRM, pool_backend_timer_handler);
		alarm(nearest);
	}
}

int connect_inet_domain_socket(int secondary_backend)
{
	char * func = "connect_inet_domain_socket()";
	int fd;
	int len;
	int on = 1;
	struct sockaddr_in addr;
	struct hostent *hp;

	fd = socket(AF_INET, SOCK_STREAM, 0);
	if (fd < 0)
	{
		show_error("%s: socket() failed: %s",func, strerror(errno));
		return -1;
	}

	/* set nodelay */
	if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY,
				   (char *) &on,
				   sizeof(on)) < 0)
	{
		show_error("%s: setsockopt() failed: %s", func, strerror(errno));
		close(fd);
		return -1;
	}

	memset((char *) &addr, 0, sizeof(addr));
	((struct sockaddr *)&addr)->sa_family = AF_INET;

	addr.sin_port = htons(CurrentCluster->port);
	len = sizeof(struct sockaddr_in);

	hp = gethostbyname(CurrentCluster->hostName);

	if ((hp == NULL) || (hp->h_addrtype != AF_INET))
	{
		show_error("%s: gethostbyname() failed: %s host: %s",func, strerror(errno), CurrentCluster->hostName);
		close(fd);
		return -1;
	}
	memmove((char *) &(addr.sin_addr),
			(char *) hp->h_addr,
			hp->h_length);

	if (connect(fd, (struct sockaddr *)&addr, len) < 0)
	{
		show_error("%s: connect() failed: %s",func,strerror(errno));
		close(fd);
		return -1;
	}
	return fd;
}

int connect_unix_domain_socket(int secondary_backend)
{
	char * func = "connect_unix_domain_socket()";
	struct sockaddr_un addr;
	int fd;
	int len;
	int port;

	fd = socket(AF_UNIX, SOCK_STREAM, 0);
	if (fd == -1)
	{
		show_error("%s: setsockopt() failed: %s", func,strerror(errno));
		return -1;
	}

	port = CurrentCluster->port;
	memset((char *) &addr, 0, sizeof(addr));
	((struct sockaddr *)&addr)->sa_family = AF_UNIX;
	snprintf(addr.sun_path, sizeof(addr.sun_path), "%s/.s.PGSQL.%d", 
			 Backend_Socket_Dir,
			 CurrentCluster->port);
#ifdef PRINT_DEBUG
	show_debug("%s:postmaster Unix domain socket: %s",func, addr.sun_path);
#endif			

	len = sizeof(struct sockaddr_un);

	if (connect(fd, (struct sockaddr *)&addr, len) < 0)
	{
		show_error("%s: connect() failed: %s",func, strerror(errno));
		close(fd);
		return -1;
	}
#ifdef PRINT_DEBUG
	show_debug("%s:connected to postmaster Unix domain socket: %s fd: %d", func,addr.sun_path, fd);
#endif			
	return fd;
}

static POOL_CONNECTION_POOL_SLOT *create_cp(POOL_CONNECTION_POOL_SLOT *cp, int secondary_backend)
{
	char * func = "create_cp()";
	int fd;
	char hostName[HOSTNAME_MAX_LENGTH];

	if (gethostname(hostName,sizeof(hostName)) < 0)
	{
		show_error("%s:gethostname() failed. (%s)",func,strerror(errno));
		return NULL;
	}
	if (PGRis_same_host(hostName,CurrentCluster->hostName) == 1)
	{
#ifdef PRINT_DEBUG
		show_debug("%s:[%s] [%s] is same",func,hostName,CurrentCluster->hostName);
#endif			
		fd = connect_unix_domain_socket(secondary_backend);
	}
	else
	{
		fd = connect_inet_domain_socket(secondary_backend);
	}

	if (fd < 0)
	{
		/* fatal error, notice to parent and exit */
		notice_backend_error();
		exit(1);
	}

	cp->con = pool_open(fd);
	cp->closetime = 0;
	return cp;
}

static POOL_CONNECTION_POOL *new_connection(POOL_CONNECTION_POOL *p)
{
	char * func = "new_connection()";
	/* create master connection */
	MASTER_CONNECTION(p) = malloc(sizeof(POOL_CONNECTION_POOL_SLOT));
	if (MASTER_CONNECTION(p) == NULL)
	{
		show_error("%s: malloc() failed [%s]",func,strerror(errno));
		return NULL;
	}
	create_cp(MASTER_CONNECTION(p), 0);

			/* initialize Paramter Status save structure */
	if (pool_init_params(&MASTER(p)->params))
	{
		return NULL;
	}
	p->num = 1;	/* number of slots */

	return p;
}

char PGRis_same_host(char * host1, char * host2)
{
	unsigned int ip1, ip2;

	if ((host1 == NULL) || (host2 == NULL))
	{
		return 0;
	}
	ip1 = PGRget_ip_by_name( host1);
	ip2 = PGRget_ip_by_name( host2);
	if (ip1 == ip2)
	{
		return 1;
	}
	return 0;
}

void pool_finish(void)
{
	char * func = "pool_finish()";
	int i;

	POOL_CONNECTION_POOL *p = pool_connection_pool;
return;
	if (p == NULL)
	{
		show_error("%s:pool_connection_pool is not initialized",func);
		return ;
	}

	for (i=0 ; i<Max_Pool ; i++)
	{
		if (p == NULL)
			break;
		/*
		if (MASTER_CONNECTION(p)->sp->user != NULL)
		{
			free(MASTER_CONNECTION(p)->sp->user);
			MASTER_CONNECTION(p)->sp->user = NULL;
		}
		if (MASTER_CONNECTION(p)->sp->database != NULL)
		{
			free(MASTER_CONNECTION(p)->sp->database);
			MASTER_CONNECTION(p)->sp->database = NULL;
		}
		if (MASTER_CONNECTION(p)->sp->startup_packet != NULL)
		{
			free(MASTER_CONNECTION(p)->sp->startup_packet);
			MASTER_CONNECTION(p)->sp->startup_packet = NULL;
		}
		*/
		if (MASTER_CONNECTION(p)->con != NULL)
		{
			pool_close(MASTER_CONNECTION(p)->con);
			MASTER_CONNECTION(p)->con = NULL;
		}
		memset(p, 0, sizeof(POOL_CONNECTION_POOL));
		p++;
	}
	free((char *)pool_connection_pool);
	pool_connection_pool = NULL;
}

