/* **********************************************************************
 * *                                                                    *
 * *         SOCKET CLIENT END INITIALIZATION FUNCTION                  *
 * *                                                                    *
 * **********************************************************************
 * File: libs/user/SocketConnect.c
 *
 *                        COPYRIGHT (c) 2004  BY
 *          SIGMET INCORPORATED, WESTFORD MASSACHUSETTS, U.S.A.
 * 
 * THIS SOFTWARE IS FURNISHED UNDER A LICENSE AND MAY BE USED AND  COPIED
 * ONLY  IN  ACCORDANCE WITH  THE  TERMS  OF  SUCH  LICENSE  AND WITH THE
 * INCLUSION OF THE ABOVE COPYRIGHT NOTICE.  THIS SOFTWARE  OR  ANY OTHER
 * COPIES  THEREOF MAY NOT BE PROVIDED OR OTHERWISE MADE AVAILABLE TO ANY
 * OTHER PERSON.  NO TITLE TO AND OWNERSHIP OF  THE  SOFTWARE  IS  HEREBY
 * TRANSFERED.
 */

#include <errno.h>
#include <fcntl.h>
#include <netdb.h>
#include <netinet/tcp.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/un.h>

#include "sigtypes.h"

#include "user_lib.h"

/* =======================================================================
 * Returns status indicating what happened.  Caller should not signal.  
 * We will signal internally based on the state transitions.
 * On success returns socket number.  On failure returns -1.  Detailed
 * error message is signalled, not returned.
 */
int SocketConnectNoBlock     /* Returns the socket */
( int iFamily_a,                /* Address family to use */
  const char *sHost_a,          /* Host name to connect to */
  UINT2       iPort_a,          /* Port number to connect to */
  void (*IoHandler_a)(int),     /* Handler for sigio signals */
  volatile UINT4 *istat_a       /* Status summary, suggest SIG_CONNECT_OK on first call */
 )
{
  int iSocket, ierror, ierrno, iflag, iaddrsize;
  struct sockaddr_un servaddr_un;
  struct sockaddr_in servaddr_in;
  struct sockaddr *pservaddr;
  struct hostent *hostptr;
  struct in_addr *paddress;

  if (iFamily_a==AF_INET)
    {
      /* Convert the server host name to an internet address:
       */
      hostptr = gethostbyname(sHost_a);
      if (hostptr != NULL)
	{
	  memset( &servaddr_in, 0, sizeof(servaddr_in));
	  servaddr_in.sin_family = iFamily_a;
	  paddress = (struct in_addr *)hostptr->h_addr_list[0];
	  servaddr_in.sin_addr = *paddress;
	  servaddr_in.sin_port = htons(iPort_a);
	  
	  pservaddr = (struct sockaddr *)(void *)&servaddr_in;
	  iaddrsize = sizeof( servaddr_in );
	}
      else
	{
	  if ( *istat_a != SIG_CONNECT_ERROR )
	    sig_signal( iMessageMod_va( USER_HOST_RESOLVE, 1, sHost_a ) );
	  *istat_a = SIG_CONNECT_ERROR;
	  return -1;
	}
    }
  else  /* Assume AF_UNIX here */
    {
      char *sptr;
      int ilength;
      sptr = getenv( "IRIS_KEYS" );
      if (sptr)
	{
	  servaddr_un.sun_family = AF_UNIX;
	  sprintf( servaddr_un.sun_path, "%siris_port%d", sptr, iPort_a );
	  ilength = strlen( servaddr_un.sun_path );

	  pservaddr = (struct sockaddr *)(void*)&servaddr_un;
	  iaddrsize = sizeof( servaddr_un ) - sizeof(servaddr_un.sun_path) + ilength;
	}
      else
	{
	  if ( *istat_a != SIG_CONNECT_ERROR )
	    sig_signal_va( USER_NO_ENV, 1, "IRIS_KEYS" );
	  *istat_a = SIG_CONNECT_ERROR;
	  return -1;
	}
    }

  iSocket = socket(iFamily_a,SOCK_STREAM,0);
  if (iSocket != -1)
    {
      /* We now have a valid opened socket, it must be closed if we exit.
       * Set the TCP_NODELAY option, if internet.
       */
      if (iFamily_a == AF_INET)
	{
	  iflag = TRUE;
	  ierror = setsockopt( iSocket, IPPROTO_TCP, TCP_NODELAY,
			       (char *)&iflag, sizeof(iflag) );
	  if (ierror < 0)
	    sig_signal( errno_to_message_va( errno, 1, "SocketConnectNoBlock(1)") ) ;
	}

      ierror = connect( iSocket, pservaddr, iaddrsize);
      if (ierror >= 0)
	{
	  struct sigaction action;
	  /* Set up the socket to be nonblocking asynchronous mode */
	  /* Set up a signal handler for the SIGIO signal */
	  action.sa_handler = IoHandler_a;
	  sigemptyset( &action.sa_mask );
	  action.sa_flags =  SA_RESTART;
	  ierror = sigaction( SIGIO, &action, NULL );
	  if (ierror == -1)
	    sig_signal( errno_to_message_va( errno, 1, "SocketConnectNoBlock(2)") );
	  
	  /* Set up so that i/o causes the SIGIO signal */
#ifdef LINUX
	  {
	    pid_t iPid=getpid();
	    ierror = fcntl(iSocket, F_SETOWN, iPid );
	    if (ierror == -1)
	      sig_signal( errno_to_message_va( errno, 1, "SocketConnectNoBlock(3)") );
	  }
	  ierror = fcntl(iSocket, F_SETFL, FASYNC);
	  if (ierror == -1)
	    sig_signal( errno_to_message_va( errno, 1, "SocketConnectNoBlock(4)") );
#else
	  iflag = getpid();
	  ierror = ioctl( iSocket, SIOCSPGRP, &iflag );
	  if (ierror == -1)
	    sig_signal( errno_to_message_va( errno, 1, "SocketConnectNoBlock(5)") );
	  iflag = 1;
	  ierror = ioctl( iSocket, FIOASYNC, &iflag );
	  if (ierror == -1)
	    sig_signal( errno_to_message_va( errno, 1, "SocketConnectNoBlock(6)") );
#endif
	  /* Set up so that i/o is nonblocking */
	  iflag = 1;
	  ierror = ioctl( iSocket, FIONBIO, &iflag );
	  if (ierror == -1)
	    sig_signal( errno_to_message_va( errno, 1, "SocketConnectNoBlock(7)") );


	  if (iFamily_a == AF_INET)
	    {
	      switch (*istat_a)
		{
		  /* If previously OK, then no signal */
		case SIG_CONNECT_NOSERVER:
		  sig_signal_va( USER_NET_SERVER_UP, 1|SIG_IGNORE_MESSAGE, sHost_a );
		  break;
		case SIG_CONNECT_NOHOST:
		case SIG_CONNECT_ERROR:
		  sig_signal_va( iMessageMod_va( USER_HOST_REACHABLE, 1, sHost_a ),
				 0|SIG_IGNORE_MESSAGE );
		  break;
		}
	    }
	    
	  *istat_a = SIG_CONNECT_OK;
	  return iSocket;
	}
      else
	{
	  ierrno = errno;
	  switch(ierrno)
	    {
	    case ENOENT:  /* Happens with AF_UNIX before bind call creates file */
	    case ECONNREFUSED:
	      if (iFamily_a == AF_INET)
		{
		  switch( *istat_a )
		    {
		      /* If previously no server, then no signal */
		    case SIG_CONNECT_OK:
		      sig_signal_va( USER_NET_SERVER_DOWN, 1, sHost_a );
		      break;
		    case SIG_CONNECT_NOHOST:
		    case SIG_CONNECT_ERROR:
		      sig_signal_va( iMessageMod_va( USER_HOST_REACHABLE, 1, sHost_a ),
				     SIG_IGNORE_MESSAGE );
		      break;
		    }
		}
	      *istat_a = SIG_CONNECT_NOSERVER;
	      break;

	    case ETIMEDOUT:
	    case EHOSTUNREACH:
	    case ENETUNREACH:
	    case ENETDOWN:
	      if (iFamily_a == AF_INET)
		{
		  MESSAGE istatus2=iMessageMod_va( USER_HOST_UNREACHABLE, 1, sHost_a );
		  switch (*istat_a)
		    {
		    case SIG_CONNECT_OK:
		    case SIG_CONNECT_NOSERVER:
		    case SIG_CONNECT_ERROR:
		      sig_signal( istatus2 );
		      break;
		      /* If previous no host, then silent signal */
		    case SIG_CONNECT_NOHOST:
		      sig_signal_va( istatus2, SIG_IGNORE_MESSAGE );
		      break;
		    }
		}
	      *istat_a = SIG_CONNECT_NOHOST;
	      break;

	    default:
	      if ( *istat_a != SIG_CONNECT_ERROR )
		sig_signal( errno_to_message_va( errno, 1, "SocketConnectNoBlock(8)" ) );
	      *istat_a = SIG_CONNECT_ERROR;
	      break;

	    }
	}

      /* Close the socket, if there was an error before returning */

      ierror = close( iSocket );
      if (ierror == -1)
	sig_signal( errno_to_message_va( errno, 1, "SocketConnectNoBlock(9)") );
      iSocket = -1;
    }
  else
    {
      if (*istat_a != SIG_CONNECT_ERROR )
	sig_signal( errno_to_message_va( errno, 1, "SocketConnectNoBlock(10)") );
      *istat_a = SIG_CONNECT_ERROR;
    }

  return iSocket;

}

/* ======================================================================
 * Function called to close the socket.
 */
void SocketDisconnect
  ( int iSocket_a )
{
  int ierror;
  struct sigaction action;

  /* Ignore option required on SGI.  Other platforms worked OK with NULL */
  action.sa_handler = SIG_IGN;
  sigemptyset( &action.sa_mask );
  action.sa_flags =  SA_RESTART;
  ierror = sigaction( SIGIO, &action, NULL );
  if (ierror == -1) sig_signal( errno_to_message(errno) );

  if (iSocket_a)
    {
      ierror = close( iSocket_a );
      if (ierror == -1) sig_signal( errno_to_message(errno) );
      iSocket_a=0;
    }

  return;

}
