/* -*-pgsql-c-*- */
/*
 *
 * $Header$
 *
 * pgpool: a language independent connection pool server for PostgreSQL 
 * written by Tatsuo Ishii
 *
 * Copyright (c) 2003-2017	PgPool Global Development Group
 *
 * 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 <unistd.h>
#include <arpa/inet.h>
#include "pool.h"
#include "utils/elog.h"
#include "context/pool_process_context.h"
#include "pool_config.h"		/* remove me afterwards */

static POOL_PROCESS_CONTEXT process_context_d;
static POOL_PROCESS_CONTEXT *process_context;

/*
 * Initialize per process context
 */
void pool_init_process_context(void)
{
	process_context = &process_context_d;

	if (!process_info)
		ereport(FATAL,
			(return_code(1),
				 errmsg("process info is not set")));
	process_context->process_info = process_info;

	if (!pool_config->backend_desc)
		ereport(FATAL,
			(return_code(1),
				 errmsg("backend desc is not set")));

	process_context->backend_desc = pool_config->backend_desc;

	process_context->proc_id = my_proc_id;

	process_context->local_session_id = 0;		/* initialize local session counter */

	process_context->last_alarm_handler = SIG_IGN;
	process_context->last_alarm_time = 0;
	process_context->last_alarm_second = 0;
	process_context->undo_alarm_second = 0;
}

/*
 * Return process context
 */
POOL_PROCESS_CONTEXT *pool_get_process_context(void)
{
	return process_context;
}

/*
 * Return my process info
 */
ProcessInfo *pool_get_my_process_info(void)
{
	if (!process_context)
		ereport(FATAL,
			(return_code(1),
				 errmsg("process context is not initialized")));

	return &process_context->process_info[process_context->proc_id];
}

/*
 * Increment local session id
 */
void pool_incremnet_local_session_id(void)
{
	POOL_PROCESS_CONTEXT *p = pool_get_process_context();

	if (!p)
		ereport(ERROR,
				(errmsg("failed to get process context")));

	p->local_session_id++;
}

/*
 * Return byte size of connection info(ConnectionInfo) on shmem.
 */
int pool_coninfo_size(void)
{
	int size;
	size = pool_config->num_init_children *
		pool_config->max_pool *
		MAX_NUM_BACKENDS *
		sizeof(ConnectionInfo);

	ereport(DEBUG1,
			(errmsg("pool_coninfo_size: num_init_children (%d) * max_pool (%d) * MAX_NUM_BACKENDS (%d) * sizeof(ConnectionInfo) (%zu) = %d bytes requested for shared memory",
					pool_config->num_init_children,
					pool_config->max_pool,
					MAX_NUM_BACKENDS,
					sizeof(ConnectionInfo),
					size)));
	return size;
}

/*
 * Return number of elements of connection info(ConnectionInfo) on shmem.
 */
int pool_coninfo_num(void)
{
	int nelm;
	nelm = pool_config->num_init_children *
		pool_config->max_pool *
		MAX_NUM_BACKENDS;

	return nelm;
}

/*
 * Return pointer to i th child, j th connection pool and k th backend
 * of connection info on shmem.
 */
ConnectionInfo *pool_coninfo(int child, int connection_pool, int backend)
{
	if (child < 0 || child >= pool_config->num_init_children)
	{
		ereport(WARNING,
			(errmsg("failed to get connection info, invalid child number: %d", child)));
		return NULL;
	}

	if (connection_pool < 0 || connection_pool >= pool_config->max_pool)
	{
		ereport(WARNING,
				(errmsg("failed to get connection info, invalid connection pool number: %d", connection_pool)));
		return NULL;
	}

	if (backend < 0 || backend >= MAX_NUM_BACKENDS)
	{
		ereport(WARNING,
				(errmsg("failed to get connection info, invalid backend number: %d", backend)));
		return NULL;
	}

	return &con_info[child*pool_config->max_pool*MAX_NUM_BACKENDS+
					 connection_pool*MAX_NUM_BACKENDS+
					 backend];
}

/*
 * Return pointer to child which has OS process id pid, j th connection
 * pool and k th backend of connection info on shmem.
 */
ConnectionInfo *pool_coninfo_pid(int pid, int connection_pool, int backend)
{
	int child = -1;
	int		i;

	for (i = 0; i < pool_config->num_init_children; i++)
	{
		if (process_info[i].pid == pid)
		{
			child = i;
			break;
		}
	}

	if (child < 0)
		elog(ERROR,"failed to get child pid, invalid child PID:%d",pid);

	if (child < 0 || child >= pool_config->num_init_children)
		elog(ERROR,"failed to get child pid, invalid child no:%d",child);

	if (connection_pool < 0 || connection_pool >= pool_config->max_pool)
		elog(ERROR,"failed to get child pid, invalid connection pool no:%d",connection_pool);

	if (backend < 0 || backend >= MAX_NUM_BACKENDS)
		elog(ERROR,"failed to get child pid, invalid backend no:%d",backend);

	return &con_info[child*pool_config->max_pool*MAX_NUM_BACKENDS+
					 connection_pool*MAX_NUM_BACKENDS+
					 backend];
}

/*
 * locate and return the shared memory ConnectionInfo having the
 * backend connection with the pid
 * if the connection is found the *backend_node_id contains the backend node id
 * of the backend node that has the connection
 */
ConnectionInfo* pool_coninfo_backend_pid(int backend_pid, int* backend_node_id)
{
	int child;
	/*
	 * look for the child process that has the backend
	 * with the pid
	 */

	ereport(DEBUG1,
			(errmsg("searching for the connection with backend pid:%d",backend_pid)));

	for (child = 0; child < pool_config->num_init_children; child++)
	{
		int pool;
		ProcessInfo *pi = pool_get_process_info(process_info[child].pid);

		for (pool = 0; pool < pool_config->max_pool; pool++)
		{
			int backend_id;
			for (backend_id = 0; backend_id < NUM_BACKENDS; backend_id++)
			{
				int poolBE = pool*MAX_NUM_BACKENDS+backend_id;
				if (ntohl(pi->connection_info[poolBE].pid) == backend_pid)
				{
					ereport(DEBUG1,
							(errmsg("found for the connection with backend pid:%d on backend node %d",backend_pid,backend_id)));
					*backend_node_id = backend_id;
					return &pi->connection_info[poolBE];
				}
			}
		}
	}
	return NULL;
}

/*
 * returns true if the conInfo object belongs to the current child process
 */
bool pool_is_my_coninfo(ConnectionInfo* connInfo)
{
	int pool;
	ProcessInfo *pi = pool_get_my_process_info();
	for (pool = 0; pool < pool_config->max_pool; pool++)
	{
		int backend_id;
		for (backend_id = 0; backend_id < NUM_BACKENDS; backend_id++)
		{
			int poolBE = pool*MAX_NUM_BACKENDS+backend_id;
			ConnectionInfo* cInfo = &pi->connection_info[poolBE];
			if (cInfo == connInfo)
			{
				ereport(DEBUG1,
						(errmsg("connection Info object is local")));
				return true;
			}
		}
	}
	return false;
}

/*
 * sets the flag to mark that the connection will be terminated by the
 * backend and it should not be considered as a backend node failure.
 * This flag is used to handle pg_terminate_backend()
 */
void pool_set_connection_will_be_terminated(ConnectionInfo* connInfo)
{
	connInfo->swallow_termination = 1;
}

void pool_unset_connection_will_be_terminated(ConnectionInfo* connInfo)
{
	connInfo->swallow_termination = 0;
}

/*
 * Set frontend connected flag
 */
void pool_coninfo_set_frontend_connected(int proc_id, int pool_index)
{
	ConnectionInfo *con;
	int i;

	for (i=0;i<NUM_BACKENDS;i++)
	{
		if (!VALID_BACKEND(i))
			continue;

		con = pool_coninfo(proc_id, pool_index, i);

		if (con == NULL)
		{
			elog(WARNING,"failed to get connection info while marking the frontend is connected for pool");
			return;
		}
		con->connected = true;
	}
}

/*
 * Unset frontend connected flag
 */
void pool_coninfo_unset_frontend_connected(int proc_id, int pool_index)
{
	ConnectionInfo *con;
	int i;

	for (i=0;i<NUM_BACKENDS;i++)
	{
		if (!VALID_BACKEND(i))
			continue;

		con = pool_coninfo(proc_id, pool_index, i);

		if (con == NULL)
		{
			elog(WARNING,"failed to get connection info while marking the frontend is not connected for pool");
			return;
		}
		con->connected = false;
	}
}

/*
 * Set an alarm clock and a signal handler.
 * For pool_alarm_undo(), the alarm second and the old handler
 * are saved, and the remaining time is calculated.
 */
void pool_alarm(pool_sighandler_t handler, unsigned int second)
{
	POOL_PROCESS_CONTEXT *p = pool_get_process_context();
	time_t	now = time(NULL);

	alarm(second);
	p->last_alarm_handler = pool_signal(SIGALRM, handler);

	if (p->last_alarm_second)
	{
		p->undo_alarm_second = p->last_alarm_second - (now - p->last_alarm_time);
		if (p->undo_alarm_second <= 0)
		  p->undo_alarm_second = 1;
	}

	p->last_alarm_time = now;
	p->last_alarm_second = second;
}

/*
 * Undo the alarm signal handler using the remaining time.
 */
void pool_undo_alarm(void)
{
	POOL_PROCESS_CONTEXT *p = pool_get_process_context();

	if (p->undo_alarm_second)
	{
		alarm(p->undo_alarm_second);
		pool_signal(SIGALRM, p->last_alarm_handler);
		p->undo_alarm_second = 0;
	}
	else
	{
		alarm(0);
		pool_signal(SIGALRM, SIG_IGN);
	}
}
