/* BEGIN_ICS_COPYRIGHT5 ****************************************

Copyright (c) 2015-2017, Intel Corporation

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

    * Redistributions of source code must retain the above copyright notice,
      this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.
    * Neither the name of Intel Corporation nor the names of its contributors
      may be used to endorse or promote products derived from this software
      without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

 * ** END_ICS_COPYRIGHT5   ****************************************/

/*******************************************************************************
 * @file cs_sockwrap.c
 *
 * @brief
 * This file contains some socket creation routines for server and client
 * AF_UNIX type sockets.
 * The routines are supposed to be used on both Unix and Vxworks but the
 * structure of the socket addresss strings passed to the routines differs
 * between the operating systems.
 * In UNIX the structure of the strings is supposed to be generally of the
 * form "/var/tmp/<unique file name>" and in VXWORKS the string should
 * take the form "/comp/socket/0xNumber" where 0xNumber is a string
 * representation of a 16 bit number is hexadecimal form.
 *******************************************************************************
 */

#include <stdlib.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/stat.h>
#include "cs_sockwrap.h"
#include <fcntl.h>
#include "cs_g.h"
#include <errno.h>
#include <string.h>

#define QLEN	10


#define	CLI_PERM	S_IRWXU			/* rwx for user only */

#define MAXWAIT  30


/*
 * @brief
 *
 * cs_local_comm_init --
 *
 * This function takes a string as an argument and creates a AF_UNIX
 * socket with the string supplied to it as the address in the socket
 * structure.
 * This routine is to be called by a program that needs a server socket.
 *
 * @param[in] name	pointer to unique socket address string
 *
 * @return *  socket descriptor on sucess or (-1) on failure
 *
 * NOTE: For VxWorks socket support, please refer to the WindRiver Network Stack
 * Programmer's Guide 6.9 (Section 5.3 - Working with Local Domain Sockets)
 *
 */
int
cs_local_comm_init(const char *name)
{
	int			fd;
	int			len;
	int			err;
	struct sockaddr_un	un;

	if (strlen(name) >= (sizeof(un.sun_path) )) {
		errno = ENAMETOOLONG;
		return(-1);
	}

	/* create a UNIX domain stream socket */
#ifdef __LINUX__
	if ((fd = socket(AF_UNIX, SOCK_SEQPACKET, 0)) < 0) {
#else
	if ((fd = socket(AF_LOCAL, SOCK_SEQPACKET, 0)) < 0) {
#endif
		return(fd);
	}

	unlink(name);	/* in case it already exists */

	/* fill in socket address structure */
	memset(&un, 0, sizeof(un));
	snprintf(un.sun_path, sizeof(un.sun_path), "%s", name);

#ifdef __LINUX__
	un.sun_family = AF_UNIX;
	len = offsetof(struct sockaddr_un, sun_path) + strlen(name);
#else
	un.sun_family = AF_LOCAL;
	len = sizeof(struct sockaddr_un);
	un.sun_len = len;
#endif

	/* bind the name */
	if (bind(fd, (struct sockaddr *)&un, len) < 0) {
		goto errout;
	}

	if (listen(fd, QLEN) < 0) {	/* tell kernel we're a server */
		goto errout;
	}
	return(fd);

errout:
	err = errno;
	close(fd);
	errno = err;
	return(-1);
}


/*
 * @brief
 *
 * cs_local_comm_accept --
 *
 * This function takes a socket descriptor as an argument and waits for
 * a client connectio to arrive on it. It accepts the new connection
 * checks for to see if the connection is via a socket and whether it
 * is writeable and returns with the new socket descriptor if everything
 * is successful
 *
 * @param[in] listenfd	socket to listen for a connection on
 *
 * @return    socket descriptor on sucess or (-1) on failure
 *
 */
int
cs_local_comm_accept(int listenfd)
{
	int				clifd;
	socklen_t			len;
	struct sockaddr_un		un;
	char				*name;
#ifdef __LINUX__
	int err;
	struct stat			statbuf;
#endif

	/* allocate enough space for longest name plus terminating null */
	if ((name = malloc(sizeof(un.sun_path) + 1)) == NULL)
		assert(name);
	len = sizeof(un);
	if ((clifd = accept(listenfd, (struct sockaddr *)&un, &len)) < 0) {
		free(name);
		return (clifd);
	}

	len -= offsetof(struct sockaddr_un, sun_path); /* len of pathname */
	cs_strlcpy(name, un.sun_path, len);

#ifdef __LINUX__
	if (stat(name, &statbuf) < 0) {
		goto errout;
	}

	/* check path to make sure its a socket and writable */
	if (S_ISSOCK(statbuf.st_mode) == 0) {
		errno = ENOTSOCK;	/* not a socket */
		goto errout;
	}

	if ((statbuf.st_mode & (S_IRWXG | S_IRWXO)) ||
		(statbuf.st_mode & S_IRWXU) != S_IRWXU) {
		  errno = EPERM;
		  goto errout;
	}
#endif

	unlink(name);
	free(name);
	return(clifd);

#ifdef __LINUX__
errout:
	err = errno;
	close(clifd);
	free(name);
	errno = err;
	return(-1);
#endif
}




/*
 * @brief
 *
 * cs_local_comm_connect --
 *
 * This function takes two string arguments which
 * are AF_UNIX socket addresses creates a new socket
 * and connects to a specified server socket.
 *
 * @param[in] srvaddr   Server socket address string
 * @param[in] claddr    Client socket address string
 *
 * @return    socket descriptor on sucess or (-1) on failure
 *
 * NOTE: For VxWorks socket support, please refer to the WindRiver Network Stack
 * Programmer's Guide 6.9 (Section 5.3 - Working with Local Domain Sockets)
 *
 */
int
cs_local_comm_connect(const char *srvaddr, const char *claddr)
{
	int			fd, len, err;
	struct sockaddr_un	un, sun;
	int			do_unlink = 0;

	if ((strlen(srvaddr) >= (sizeof(un.sun_path)) ||
	     (strlen(claddr) >= sizeof(un.sun_path)))) {
		errno = ENAMETOOLONG;
		return(-1);
	}

	/* create a UNIX domain stream socket */
#ifdef __LINUX__
	if ((fd = socket(AF_UNIX, SOCK_SEQPACKET, 0)) < 0) {
#else
	if ((fd = socket(AF_LOCAL, SOCK_SEQPACKET, 0)) < 0) {
#endif
		return(-1);
	}

	/* fill socket address structure with our address */
	memset(&un, 0, sizeof(un));
	cs_strlcpy(un.sun_path, claddr, sizeof(un.sun_path));

#ifdef __LINUX__
	un.sun_family = AF_UNIX;
	len = offsetof(struct sockaddr_un, sun_path) + strlen(un.sun_path);
	unlink(un.sun_path);		/* in case it already exists */
#else
	un.sun_family = AF_LOCAL;
	len = sizeof(struct sockaddr_un);
	un.sun_len = len;
#endif

	if (bind(fd, (struct sockaddr *)&un, len) < 0) {
		goto errout;
	}

#ifdef __LINUX__
	if (chmod(un.sun_path, CLI_PERM) < 0) {
		do_unlink = 1;
		goto errout;
	}
#endif

	/* fill socket address structure with server's address */
	memset(&sun, 0, sizeof(sun));
	cs_strlcpy(sun.sun_path, srvaddr, sizeof(un.sun_path));

#ifdef __LINUX__
	sun.sun_family = AF_UNIX;
	len = offsetof(struct sockaddr_un, sun_path) + strlen(srvaddr);
#else
	sun.sun_family = AF_LOCAL;
	len = sizeof(struct sockaddr_un);
	sun.sun_len = len;
#endif
	if (connect(fd, (struct sockaddr *)&sun, len) < 0) {
		do_unlink = 1;
		goto errout;
	}
	return(fd);

errout:
	err = errno;
	close(fd);
	if (do_unlink)
		unlink(un.sun_path);
	errno = err;
	return(-1);
}

/**
 * @brief
 * cs_comm_connect --
 *
 * This function creates a socket with characteristics of the type requested
 * and then connects to the specified channel using that socket. An explicit
 * bind by the client is not done as the connect will bind a default address.
 * If the connection fails because of an unready or a busy server it retries
 * the connection in an exponential  form.
 *
 * @param[in]  domain   Socket domain
 * @param[in]  type     Socket type
 * @param[in]  protocol Usually 0
 * @param[in]  srv_addr     sockaddr structure of server
 * @param[in]  srv_len      len of server sockaddr
 *
 *
 * @return   socket fd is successful, -1 if not
 */
static int
cs_comm_connect(int domain, int type, int protocol,
		const struct sockaddr *srv_addr, socklen_t srv_len)
{
	int wait = 0;
	int fd = -1;

	if ((fd = socket(domain, type, protocol) <0)) {
	    return (-1);
	}

	while  (wait++ < MAXWAIT) {
		if (connect(fd, srv_addr, srv_len) == 0) {
			return (fd);
		}else {
			if (errno != ETIMEDOUT &&
			    errno != ECONNREFUSED) {
				goto exit_func;
			}
		}

		sleep(MAXWAIT - wait);
	}
exit_func:
	close(fd);
	return (-1);

}

/**
 * @brief
 * cs_tcp_comm_connect --
 *
 * This function initiates a tcp connect stream connection.
 * Used for demo purposes at this point.
 *
 * @param[in]  srv_addr     sockaddr structure of server
 * @param[in]  srv_len      len of server sockaddr
 *
 * @return   socket fd is successful, -1 if not
 */
int
cs_tcp_comm_connect(const struct sockaddr *srv_addr, socklen_t srv_len)
{
	return (cs_comm_connect(AF_INET, SOCK_STREAM, 0, srv_addr, srv_len));
}

/**
 * @brief
 * cs_udp_comm_connect --
 *
 * This function initiates a udp connect stream connection.
 * Used for demo purposes at this point.
 *
 * @param[in]  srv_addr     sockaddr structure of server
 * @param[in]  srv_len      len of server sockaddr
 *
 * @return   socket fd is successful, -1 if not
 */
int
cs_udp_comm_connect(const struct sockaddr *srv_addr, socklen_t srv_len)
{
	return (cs_comm_connect(AF_INET, SOCK_DGRAM, 0, srv_addr, srv_len));
}

/**
 * @brief
 *
 * cs_set_fd_non_block --
 *
 * This function sets the fd passed in to non-blocking.
 *
 * @params[in]  fd  file descriptor
 *
 * @return	1 on success, (-1) on failure
 *
 */
int
cs_set_fd_non_block(int fd)
{
	/* TBD on vxworks */
#ifdef __LINUX__
	int options;

	if ((options = fcntl(fd, F_GETFL)) < 0) {
		IB_LOG_ERROR_FMT(__func__, "fcntl get Error %s\n",
						strerror(errno));
		return (-1);
	}

	options |= O_NONBLOCK;

	if (fcntl(fd, F_SETFL, options) < 0) {
		IB_LOG_ERROR_FMT(__func__, "fcntl set Error %s\n",
						strerror(errno));
		return (-1);
	}
#endif

	return (1);
}


