/***********************************************************************
*                                                                      *
*               This software is part of the ast package               *
*           Copyright (c) 1990-2006 AT&T Knowledge Ventures            *
*                      and is licensed under the                       *
*                  Common Public License, Version 1.0                  *
*                      by AT&T Knowledge Ventures                      *
*                                                                      *
*                A copy of the License is available at                 *
*            http://www.opensource.org/licenses/cpl1.0.txt             *
*         (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9)         *
*                                                                      *
*              Information and Software Systems Research               *
*                            AT&T Research                             *
*                           Florham Park NJ                            *
*                                                                      *
*                 Glenn Fowler <gsf@research.att.com>                  *
*                                                                      *
***********************************************************************/
#pragma prototyped
/*
 * Glenn Fowler
 * AT&T Research
 *
 * internet address/port binding
 */

#include "cslib.h"

/*
 * low level for csbind()
 */

static int
portbind(register Cs_t* state, const char* type, unsigned long addr, unsigned int port)
{

#if CS_LIB_V10

	int		fd;
	struct tcpuser	tcp;

	if (streq(type, "udp"))
	{
		if (!addr)
		{
			if ((fd = udp_datagram(port)) < 0)
				messagef((state->id, NiL, -1, "bind: %s: udp_datagram error", type));
			return fd;
		}
		if (addr == CS_LOCAL) addr = 0;
		if ((fd = udp_connect(0, addr, port)) < 0)
			messagef((state->id, NiL, -1, "bind: %s: udp_connect error", type));
		return fd;
	}
	else if (streq(type, "tcp"))
	{
		if ((fd = tcp_sock()) < 0)
		{
			messagef((state->id, NiL, -1, "bind: %s: tcp_sock error", type));
			return -1;
		}
		memzero(&tcp, sizeof(tcp));
		tcp.fport = port;
		if (addr != CS_LOCAL) tcp.faddr = addr;
		if (addr ? !tcp_connect(fd, &tcp) : !tcp_listen(fd, &tcp))
		{
			state->addr = tcp.laddr;
			state->port = tcp.lport;
			return fd;
		}
		messagef((state->id, NiL, -1, "bind: %s %s error", type, addr ? "tcp_connect" : "tcp_listen"));
		close(fd);
	}

#else

#if CS_LIB_SOCKET

	int			fd;
	int			sock;
	int			r;
	struct sockaddr_in	nam;
	Sock_size_t		namlen = sizeof(nam);

	if (streq(type, "tcp")) sock = SOCK_STREAM;
	else if (streq(type, "udp")) sock = SOCK_DGRAM;
	else
	{
		messagef((state->id, NiL, -1, "bind: %s: invalid type", type));
		return -1;
	}
	if ((fd = socket(AF_INET, sock, 0)) < 0)
	{
		messagef((state->id, NiL, -1, "bind: %s: AF_INET socket error", type));
		return -1;
	}
	memzero(&nam, sizeof(nam));
	nam.sin_family = AF_INET;
	if (addr != CS_LOCAL) nam.sin_addr.s_addr = addr;
	state->addr = addr;
	state->port = port;
	nam.sin_port = htons(port);
	if (addr)
	{
#if defined(O_NONBLOCK) || defined(FNDELAY)
		int	fl;
#endif
#if defined(IPPROTO_TCP) && defined(TCP_NODELAY)
		int	sl;
#endif
		if (state->flags & CS_ADDR_NOW)
		{
#if defined(O_NONBLOCK) || defined(FNDELAY)
			if ((fl = fcntl(fd, F_GETFL, 0)) != -1)
#if defined(FNDELAY)
				fcntl(fd, F_SETFL, fl|FNDELAY);
#else
				fcntl(fd, F_SETFL, fl|O_NONBLOCK);
#endif
#endif
#if defined(IPPROTO_TCP) && defined(TCP_NODELAY)
			if (sock == SOCK_STREAM)
			{
				sl = 1;
				setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (const char*)&sl, sizeof(sl));
			}
#endif
		}
		if (!(r = connect(fd, (struct sockaddr*)&nam, sizeof(nam)))
#ifdef EINPROGRESS
		    || errno == EINPROGRESS
#endif
			)
		{
			if (!r && (state->flags & CS_ADDR_NOW))
			{
#if defined(O_NONBLOCK) || defined(FNDELAY)
				if (fl != -1)
					fcntl(fd, F_SETFL, fl);
#endif
#if defined(IPPROTO_TCP) && defined(TCP_NODELAY)
				if (sock == SOCK_STREAM)
				{
					sl = 0;
					setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (const char*)&sl, sizeof(sl));
				}
#endif
			}
			state->addr = nam.sin_addr.s_addr;
			state->port = port;
			return fd;
		}
		messagef((state->id, NiL, -1, "bind: %s: connect error", type));
		if (errno == EADDRNOTAVAIL || errno == ECONNREFUSED)
			errno = ENOENT;
	}
	else
	{
#ifdef SO_REUSEADDR
		if (state->port != CS_PORT_NORMAL)
		{
			int	n = 1;

			setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (const char*)&n, sizeof(n));
		}
#endif
		if (!bind(fd, (struct sockaddr*)&nam, sizeof(nam)) && (sock != SOCK_STREAM || !listen(fd, 32)))
		{
			if (!getsockname(fd, (struct sockaddr*)&nam, &namlen) && namlen == sizeof(nam))
			{
				state->addr = nam.sin_addr.s_addr;
				state->port = ntohs((unsigned short)nam.sin_port);
			}
			else messagef((state->id, NiL, -1, "bind: %s: getsockname error", type));
			return fd;
		}
		messagef((state->id, NiL, -1, "bind: %s: bind error", type));
		if (errno == EADDRINUSE) errno = EEXIST;
	}
	close(fd);

#else

	errno = ENOTDIR;
	messagef((state->id, NiL, -1, "bind: %s: not supported", type));

#endif

#endif

	return -1;
}

/*
 * create [addr==0] or open stream fd for <type,addr,port>
 * for create
 *
 *	addr	CS_LOCAL for local address
 *
 *	port	CS_PORT_NORMAL for normal port allocation
 *		CS_PORT_RESERVED for reserved port allocation
 */

int
csbind(register Cs_t* state, const char* type, unsigned long addr, unsigned long port, unsigned long clone)
{
	int	fd;

	messagef((state->id, NiL, -8, "bind(%s,%s,%u,%lu) call", type, csntoa(state, addr), port, clone));
	if (port == CS_PORT_INVALID)
		return -1;
	if (port == CS_PORT_RESERVED)
	{
		static unsigned int	last = IPPORT_RESERVED;

		if (addr)
		{
			errno = EROFS;
			messagef((state->id, NiL, -1, "bind: %s: explicit reserved port invalid", csntoa(state, addr)));
			return -1;
		}
		port = last;
		do
		{
			if (--last <= IPPORT_RESERVED / 2)
				port = IPPORT_RESERVED - 1;
			if (last == port)
			{
				errno = ENOSPC;
				messagef((state->id, NiL, -1, "bind: no more reserved ports"));
				break;
			}
			if ((fd = portbind(state, type, 0L, last)) >= 0)
				goto ok;
		} while (errno != EACCES);
		messagef((state->id, NiL, -1, "bind: reserved port allocation error"));
		return -1;
	}
	if (port == CS_PORT_NORMAL && addr)
	{
		errno = EROFS;
		return -1;
	}
	if ((fd = portbind(state, type, addr, port)) < 0)
		return -1;
	if (clone)
	{
		int	n;
		char	buf[16];

		n = sfsprintf(buf, sizeof(buf), "%d %lu\n", CS_KEY_CLONE, clone);
		cswrite(state, fd, buf, n);
	}
 ok:
	messagef((state->id, NiL, -8, "bind(%s,%s,%u,%lu) = %d", type, csntoa(state, addr), port, clone, fd));
	return fd;
}

int
_cs_bind(const char* type, unsigned long addr, unsigned long port, unsigned long clone)
{
	return csbind(&cs, type, addr, port, clone);
}
