/*
 *
 * Copyright 1998-1999, University of Notre Dame.
 * Authors: Jeffrey M. Squyres, Kinis L. Meyer with M. D. McNally 
 *          and Andrew Lumsdaine
 *
 * This file is part of the Notre Dame LAM implementation of MPI.
 *
 * You should have received a copy of the License Agreement for the
 * Notre Dame LAM implementation of MPI along with the software; see
 * the file LICENSE.  If not, contact Office of Research, University
 * of Notre Dame, Notre Dame, IN 46556.
 *
 * Permission to modify the code and to distribute modified code is
 * granted, provided the text of this NOTICE is retained, a notice that
 * the code was modified is included with the above COPYRIGHT NOTICE and
 * with the COPYRIGHT NOTICE in the LICENSE file, and that the LICENSE
 * file is distributed with the modified code.
 *
 * LICENSOR MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED.
 * By way of example, but not limitation, Licensor MAKES NO
 * REPRESENTATIONS OR WARRANTIES OF MERCHANTABILITY OR FITNESS FOR ANY
 * PARTICULAR PURPOSE OR THAT THE USE OF THE LICENSED SOFTWARE COMPONENTS
 * OR DOCUMENTATION WILL NOT INFRINGE ANY PATENTS, COPYRIGHTS, TRADEMARKS
 * OR OTHER RIGHTS.  
 *
 * Additional copyrights may follow.
 *
 *	Software for Humanity
 *	RBD/GDB/NJN
 *
 *	This program is freely distributed in the hope that it will be useful,
 *	but WITHOUT ANY WARRANTY; without even the implied warranty of
 *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *
 *	$Id: socket.c,v 6.7 1999/08/30 21:38:26 jsquyres Exp $
 *
 *	Function:	- socket utilities
 */

#include <lam_config.h>
#include <sfh.h>

#include <errno.h>
#include <netdb.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/time.h>
#include <sys/un.h>
#include <unistd.h>

#if LAM_NEED_SYS_SELECT_H
#include <sys/select.h>
#endif

#include <portable.h>
#include <typical.h>

/*
 * public functions
 */
int			sfh_sock_open_srv_inet_stm();
int			sfh_sock_open_clt_inet_stm();
int			sfh_sock_open_srv_unix_stm();
int			sfh_sock_open_clt_unix_stm();
int			sfh_sock_accept_tmout();
int			sfh_sock_open_srv_inet_dgm();
int			sfh_sock_open_clt_inet_dgm();
void			sfh_sock_fill_inet_addr();
int			sfh_sock_set_buf_size();

/*
 *	sfh_sock_open_srv_inet_stm
 *
 *	Function:	- create a server-side socket
 *			- Internet Domain, Stream connection
 *			- if port points to a non-zero positive port
 *			  number, that port number is used otherwise a
 *			  port number is dynamically allocated
 *			- if port is non-NULL, the server port number
 *			  is returned in it
 *	Accepts:	- ptr to port number (or NULL)
 *	Returns:	- socket descriptor or -1
 */
int
sfh_sock_open_srv_inet_stm(port)

int			*port;

{
	int		sockd;		/* socket descriptor */
	LAM_SOCK_OPTLEN_T length;	/* length of socket name */
	struct sockaddr_in
			srvaddr;	/* server address info */
/*
 * Create server socket.
 */
	if ((sockd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
		return(-1);
	}
/*
 * Bind the socket.  If a port # is given use it otherwise bind allocates one.
 */
	memset((char *) &srvaddr, 0, sizeof(srvaddr));
	srvaddr.sin_family = AF_INET;
	srvaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	srvaddr.sin_port = (port && (*port > 0)) ?
			htons((unsigned short) *port) : htons(0);

	if (bind(sockd, (struct sockaddr *) &srvaddr, sizeof(srvaddr))) {
		close(sockd);
		return(-1);
	}
/*
 * If bind allocated a port # dynamically and port is non-NULL,
 * get the port # allocated and return it in port.
 */
	if (port && (*port <= 0)) {
		length = sizeof(srvaddr);
		if (getsockname(sockd, (struct sockaddr *) &srvaddr, &length)) {
			close(sockd);
			return(-1);
		}

		*port = (int) ntohs(srvaddr.sin_port);
	}
/*
 * Create backlog queue.
 */
	if (listen(sockd, 5)) {
		close(sockd);
		return(-1);
	}

	return(sockd);
}

/*
 *	sfh_sock_open_clt_inet_stm
 *
 *	Function:	- create a client-side socket
 *			- Internet Domain, Stream connection
 *	Accepts:	- server host address (4 bytes, in network byte order)
 *			- server port number
 *	Returns:	- socket descriptor or -1
 */
int
sfh_sock_open_clt_inet_stm(hostaddr, portnum)

unsigned char		*hostaddr;
int			portnum;

{
	int		sockd;		/* socket descriptor */
	int		ret;		/* connect() return value */
	struct sockaddr_in
			srvaddr;	/* server address info */
/*
 * Loop until successful at creating and connecting
 * a socket with no interruption.
 */
	for (;;) {
/*
 * Create client socket.
 */
		if ((sockd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
			return(-1);
		}
/*
 * Setup the server Internet address structure.
 */
		memset((char *) &srvaddr, 0, sizeof(srvaddr));
		srvaddr.sin_family = AF_INET;
		srvaddr.sin_port = htons((unsigned short) portnum);
		memcpy((char *) &srvaddr.sin_addr, (char *) hostaddr, 4);
/*
 * Connect client socket to server socket.
 */
		ret = connect(sockd,
			(struct sockaddr *) &srvaddr, sizeof(srvaddr));

		if (ret == 0) {
			return(sockd);
		} else if (errno != EINTR) {
			close(sockd);
			return(-1);
		} else {
			close(sockd);
		}
	}
}

/*
 *	sfh_sock_open_srv_unix_stm
 *
 *	Function:	- create a server-side socket
 *			- Unix Domain, Stream connection
 *			- if addr is not a zero length string then
 *			  it is used as the socket address otherwise it must
 *			  have room for at least 17 characters and a unique
 *			  address is generated and returned in it
 *	Accepts:	- address string
 *	Returns:	- socket descriptor or -1
 */
int
sfh_sock_open_srv_unix_stm(addr)

char			*addr;

{
	int		len;
	int		sd;
	struct sockaddr_un
			server_un;		/* server address */
/*
 * If an address was not given create one.
 * String addr must have room for 17 chars (16 in tmp name + 1 for null char).
 */
	if (strlen(addr) == 0) {
		strcpy(addr, "/tmp/sfh-sXXXXXX");
		if (mktemp(addr) == 0) {
			return(-1);
		}
	}
/*
 * Create server socket.
 */
	if ((sd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
		return(-1);
	}
/*
 * Bind the socket.
 */

	memset(&server_un, 0, sizeof(server_un));
	server_un.sun_family = AF_UNIX;
	strcpy(server_un.sun_path, addr);

#if LAM_HAVE_SA_LEN
	len = sizeof(server_un.sun_len) + sizeof(server_un.sun_family) +
		strlen(server_un.sun_path) + 1;
	server_un.sun_len = len;
#else
	len = strlen(server_un.sun_path) + sizeof(server_un.sun_family);
#endif

	if (bind(sd, (struct sockaddr *) &server_un, len) < 0) {
		close(sd);
		return(-1);
	}
/*
 * Create backlog queue.
 */
	if (listen(sd, 5) < 0) {
		close(sd);
		return(-1);
	}

	return (sd);
}

/*
 *	sfh_sock_open_clt_unix_stm
 *
 *	Function:	- connects stream, UNIX, client socket
 *			- retries after certain error conditions
 *	Accepts:	- UNIX address (filename)
 *	Returns:	- socket or -1
 */
int
sfh_sock_open_clt_unix_stm(addr)

char			*addr;

{
	int		len;
	int		sd;
	int		r;
	struct sockaddr_un
			server_un;		/* server address */
/*
 * Set up address.
 */
	memset(&server_un, 0, sizeof(server_un));
	server_un.sun_family = AF_UNIX;
	strcpy(server_un.sun_path, addr);

#if LAM_HAVE_SA_LEN
	len = sizeof(server_un.sun_len)
		+ sizeof(server_un.sun_family) + strlen(server_un.sun_path) + 1;
	server_un.sun_len = len;
#else
	len = strlen(server_un.sun_path) + sizeof(server_un.sun_family);
#endif
/*
 * Create the socket.
 */
	do {
		if ((sd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
				return(-1);
/*
 * Connect to server.
 */
		r = connect(sd, (struct sockaddr *) &server_un, len);

		if (r < 0) {
			if ((errno != ECONNREFUSED) && (errno != EINTR) &&
					(errno != ETIMEDOUT)) {
				close(sd);
				return(-1);
			}

			if (close(sd)) return(-1);
		}
	} while (r < 0);

	return(sd);
}

/*
 *	sfh_sock_accept_tmout
 *
 *	Function:	- accept a connection with a timeout
 *			- negative timeout values block indefinitely
 *	Accepts:	- socket
 *			- timeout value (seconds)
 *	Returns:	- connection socket or -1
 */
int
sfh_sock_accept_tmout(sock, timeout)

int			sock;
int			timeout;

{
	int		connsock;	/* connection socket */
	int		ret;		/* select return value */
	fd_set		readmask;	/* select read-mask */
	struct timeval	tmout;		/* timeout argument */

	if (timeout >= 0) {
/*
 * Set up the timeout argument and block on a select call.
 */
		tmout.tv_sec = timeout;
		tmout.tv_usec = 0;

		FD_ZERO(&readmask);
		FD_SET(sock, &readmask);

		while (((ret = select(sock + 1, &readmask, (fd_set *) 0,
			(fd_set *) 0, &tmout)) < 0) && (errno == EINTR));

		if (ret <= 0) {
			errno = (ret == 0) ? ETIMEDOUT : errno;
			return(-1);
		}
	}
/*
 * Do the accept call.
 */
	while (((connsock = accept(sock, (struct sockaddr *) 0,
			(LAM_SOCK_OPTLEN_T *) 0)) < 0) && (errno == EINTR));

	return(connsock);
}

/*
 *	sfh_sock_open_srv_inet_dgm
 *
 *	Function:	- create a server-side socket
 *			- Internet domain datagrams
 *			- if port points to a non-zero positive port
 *			  number, that port number is used otherwise a
 *			  port number is dynamically allocated
 *			- if port is non-NULL, the server port number
 *			  is returned in it
 *	Accepts:	- ptr to port number (or NULL)
 *	Returns:	- socket descriptor or -1
 */
int
sfh_sock_open_srv_inet_dgm(port)

int			*port;

{
	int		sockd;		/* socket descriptor */
	LAM_SOCK_OPTLEN_T length;		/* length of socket name */
	struct sockaddr_in
			srvaddr;	/* server address info */
/*
 * Create server socket.
 */
	if ((sockd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
		return(-1);
	}
/*
 * Bind the socket.  If a port # is given use it otherwise bind allocates one.
 */
	sfh_sock_fill_inet_addr((unsigned char *) 0,
				(port) ? *port : 0, &srvaddr);

	if (bind(sockd, (struct sockaddr *) &srvaddr, sizeof(srvaddr))) {
		close(sockd);
		return(-1);
	}
/*
 * If bind allocated a port # dynamically and port is non-NULL,
 * get the port # allocated and return it in port.
 */
	if (port && (*port <= 0)) {
		length = sizeof(srvaddr);
		if (getsockname(sockd,
				(struct sockaddr *) &srvaddr, &length)) {
			close(sockd);
			return(-1);
		}

		*port = (int) ntohs(srvaddr.sin_port);
	}

	return(sockd);
}


/*
 *	sfh_sock_open_clt_inet_dgm
 *
 *	Function:	- create a client-side socket
 *			- Internet domain datagrams
 *	Returns:	- socket descriptor or -1
 */
int
sfh_sock_open_clt_inet_dgm()

{
	int		sockd;		/* socket descriptor */
	struct sockaddr_in
			myaddr;		/* local address info */
/*
 * Create client socket.
 */
	sockd = socket(AF_INET, SOCK_DGRAM, 0);
	if (sockd < 0) {
		return(-1);
	}
/*
 * Bind local address.
 */
	sfh_sock_fill_inet_addr((unsigned char *) 0, 0, &myaddr);

	if (bind(sockd, (struct sockaddr *) &myaddr, sizeof(myaddr))) {
		close(sockd);
		return(-1);
	}

	return(sockd);
}


/*
 *	sfh_sock_fill_inet_addr
 *
 *	Function:	- fill host/port Internet address structure
 *	Accepts:	- host address or NULL (4 bytes, network byte order)
 *			- port number
 *			- ptr Internet address structure
 */
void
sfh_sock_fill_inet_addr(hostaddr, port, addr)

unsigned char		*hostaddr;
int			port;
struct sockaddr_in	*addr;

{
	memset((char *) addr, 0, sizeof(struct sockaddr_in));
	addr->sin_family = AF_INET;
	addr->sin_port = (port > 0) ? htons((unsigned short) port) : htons(0);
	if (hostaddr) {
		memcpy((char *) &(addr->sin_addr), (char *) hostaddr, 4);
	} else {
		addr->sin_addr.s_addr = htonl(INADDR_ANY);
	}
}

/*
 *	sfh_sock_set_buf_size
 *
 *	Function:	- set socket buffer size
 *	Accepts:	- socket fd
 *			- domain (SFH_INET or SFH_UNIX)
 *			- buffer (SO_SNDBUF or SO_RCVBUF)
 *			- size to set (bytes)
 */
int
sfh_sock_set_buf_size(sd, domain, buf, size)

int			sd;
int			domain;
int			buf;
int			size;

{
    LAM_SOCK_OPTLEN_T 	optlen;
    int			defsize = 0;

    if (domain == SFH_UNIX) {
#if LAM_BROKEN_SET_UNIX_SO_BUFSIZES
	return(0);
#endif
    } else if (domain == SFH_INET) {
#if LAM_BROKEN_SET_INET_SO_BUFSIZES
	return(0);
#endif
    } else {
	errno = EINVAL;
	return(-1);
    }

    optlen = sizeof(defsize);
    if (getsockopt(sd, SOL_SOCKET, buf, (void *) &defsize, &optlen)) {
	return(-1);
    }

    if (defsize < size) {
	if (setsockopt(sd, SOL_SOCKET, buf, (void *) &size, sizeof(size))) {
	    return(-1);
	}
    }

    return(0);
}
