/****************************************************************
 *								*
 *	Copyright 2001, 2009 Fidelity Information Services, Inc	*
 *								*
 *	This source code contains the intellectual property	*
 *	of its copyright holder(s), and is made available	*
 *	under a license.  If you do not know the terms of	*
 *	the license, please stop and do not read further.	*
 *								*
 ****************************************************************/

/* iotcp_list.c - routines to maintain a list of TCP/IP listening sockets */

#include "mdef.h"

#include "gtm_stdio.h"
#include "gtm_string.h"
#include "gtm_socket.h"
#include "gtm_inet.h"

#include <errno.h>

#include "io_params.h"
#include "io.h"
#include "iotcproutine.h"
#include "iotcpdef.h"

/* list of listening sockets */
typedef struct	lsock_rec_s
{
	int			socket;
	struct sockaddr_in	sin;
	struct lsock_rec_s	*next;
	io_log_name		*ldev;		/* listening device record */
} lsock_rec;

static lsock_rec		*lsock_list = NULL;

GBLREF tcp_library_struct        tcp_routines;

int	iotcp_newlsock(io_log_name *dev, d_tcp_struct *tcpptr);

/* find the listening socket associated with the address of this
 * new socket, creating a listening socket if there is none.
 */
int	iotcp_getlsock(io_log_name *dev)
{
	d_tcp_struct *tcpptr;
	lsock_rec *ls;

#ifdef DEBUG_TCP
	PRINTF("iotcp_getlsock ---\n");
#endif

	tcpptr = (d_tcp_struct *)dev->iod->dev_sp;

	for (ls = lsock_list;  ls != NULL;  ls = ls->next)
	if (tcpptr->sin.sin_addr.s_addr == ls->sin.sin_addr.s_addr  &&  tcpptr->sin.sin_port == ls->sin.sin_port)
		return ls->socket;

	return iotcp_newlsock(dev, tcpptr);
}


int	iotcp_newlsock(io_log_name *dev, d_tcp_struct *tcpptr)
{
	lsock_rec	*new_lsock;

	io_log_name	*ldev;		/* listening device */
	d_tcp_struct	*lsock_tcp;
	int		lsock;
	char		buf[MAX_TRANS_NAME_LEN];
	static char	ones[] = {1, 1, 1};
	mstr		ldev_name;
	int		on = 1;
	char		*errptr;
	int4		errlen;

	error_def(ERR_SOCKINIT);
	error_def(ERR_TEXT);


#ifdef DEBUG_TCP
	PRINTF("iotcp_newlsock ---\n");
#endif

	lsock = tcp_routines.aa_socket(AF_INET, SOCK_STREAM, 0);
	if (lsock == -1)
	{
		errptr = (char *)STRERROR(errno);
		errlen = STRLEN(errptr);
		rts_error(VARLSTCNT(5) ERR_SOCKINIT, 3, errno, errlen, errptr);
		return 0;
	}

	/* allow multiple connections to the same IP address */
	if (tcp_routines.aa_setsockopt(lsock, SOL_SOCKET, SO_REUSEADDR, &on, SIZEOF(on)) == -1)
	{
		errptr = (char *)STRERROR(errno);
		errlen = STRLEN(errptr);
		(void)tcp_routines.aa_close(lsock);
		rts_error(VARLSTCNT(5) ERR_SOCKINIT, 3, errno, errlen, errptr);
		return 0;
	}

	if (tcp_routines.aa_bind(lsock, (struct sockaddr *)&tcpptr->sin, SIZEOF(struct sockaddr)) == -1)
	{
		errptr = (char *)STRERROR(errno);
		errlen = STRLEN(errptr);
		(void)tcp_routines.aa_close(lsock);
		rts_error(VARLSTCNT(5) ERR_SOCKINIT, 3, errno, errlen, errptr);
		return 0;
	}

	/* establish a queue of length 5 for incoming connections */
	if (tcp_routines.aa_listen(lsock, 5) == -1)
	{
		errptr = (char *)STRERROR(errno);
		errlen = STRLEN(errptr);
		(void)tcp_routines.aa_close(lsock);
		rts_error(VARLSTCNT(5) ERR_SOCKINIT, 3, errno, errlen, errptr);
		return 0;
	}

	/* create an extra device for the listening socket (device
	 * name is the user-specified device name with three ^A's
	 * appended).  We need this device to be on the io_log_name
	 * list so that it gets closed automatically when the user's
	 * program exits.
	 */
	ldev_name.addr=buf;
	memcpy(ldev_name.addr, dev->dollar_io, dev->len);
	memcpy(ldev_name.addr+dev->len, ones, 3);
	ldev_name.len = dev->len + 2;
	ldev = get_log_name(&ldev_name, INSERT);

	/* copy all state information from the current device */

	/* io descriptor */
	ldev->iod = (io_desc *)malloc(SIZEOF(io_desc));
	memcpy(ldev->iod, dev->iod, SIZEOF(io_desc));
	ldev->iod->state = dev_open;
	ldev->iod->pair.in  = ldev->iod;
	ldev->iod->pair.out = ldev->iod;

	/* tcp-specific information */
	ldev->iod->dev_sp = (void *)malloc(SIZEOF(d_tcp_struct));
	lsock_tcp=(d_tcp_struct *)ldev->iod->dev_sp;
	memcpy(lsock_tcp, tcpptr, SIZEOF(d_tcp_struct));

	lsock_tcp->socket = lsock;
	ldev->iod->state = dev_open;

	/* add to our list of tcp listening sockets */
	new_lsock = (lsock_rec *)malloc(SIZEOF(lsock_rec));
	new_lsock->socket = lsock;
	new_lsock->sin = tcpptr->sin;
	new_lsock->next = lsock_list;
	new_lsock->ldev = ldev;
	lsock_list = new_lsock;

	return lsock;
}


void iotcp_rmlsock(io_desc *iod)
{
	lsock_rec	*ls, *prev, *next;
	d_tcp_struct	*tcpptr = (d_tcp_struct *)iod->dev_sp;

	for (prev = NULL, ls = lsock_list;  ls != NULL;)
	{
		next = ls->next;
		if (tcpptr->sin.sin_port == ls->sin.sin_port)
		{
			if (prev)
				prev->next = ls->next;
			else
				lsock_list = ls->next;
			tcp_routines.aa_close(ls->socket);
			ls->ldev->iod->state = dev_closed;
			free(ls);
		}
		else
			prev = ls;
		ls = next;
	}
}
