/* ********************************************
 * *                                          *
 * *       SOCKET SUPPORT ROUTINES            *
 * *                                          *
 * ********************************************
 * File: libs/user/SocketSupport.C
 *
 *                       COPYRIGHT (c) 2003  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 <netinet/in.h>
#include <netinet/tcp.h>
#define INCLUDE_STDIO_PROTOTYPES 1
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#define INCLUDE_SYSSOCKET_PROTOTYPES 1
#include <sys/socket.h>
#include <unistd.h>

#include "sigtypes.h"

#include "user_lib.h"

static void SocketSigioHandler( int signal_a );

/* ==============================================
 * Handle the socket server listen configuration for a blocking
 * socket.
 */
void SocketListenBlock
(UINT2 iPort_a,             /* Port number to listen on */
 ConnectionTestProc    ConnectionTester_a,
 ConnectionHandlerProc ConnectionHandler_a,
 ParentHandlerProc     ParentHandler_a
 )
{
  int sockfd, ierror, iflag;
  struct sockaddr_in servaddr;
  UINT2 laddr_in_use;
  static UINT2 last_addr_in_use = FALSE; /*last call got the error*/
  static UINT2 lsignalled_already;       /*Error was already signalled*/
  static UINT4 ifirst_time;

  /* ------------------------------------------ */

  laddr_in_use = FALSE;
  sockfd = socket(AF_INET,SOCK_STREAM,0);
  if (sockfd == -1)
    {
      sig_signal( errno_to_message_va( errno, 1, "socket()" ) ) ;
      last_addr_in_use = laddr_in_use;
      return;
    }

  /* We now have a valid opened socket, it must be closed if we exit.
   * Set the TCP_NODELAY option.
   */
  iflag = TRUE;
  ierror = setsockopt( sockfd, IPPROTO_TCP, TCP_NODELAY, (char *)&iflag, sizeof(iflag) );
  if (ierror < 0)
    sig_signal( errno_to_message_va( errno, 1, "setsockopt()") ) ;

  /* We will use blocking I/O on the socket! */

  memset( &servaddr, 0, sizeof(servaddr));
  servaddr.sin_family = AF_INET;
  servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
  servaddr.sin_port = htons(iPort_a);
  ierror = bind(sockfd, (struct sockaddr *)(void*)&servaddr, sizeof(servaddr));
  if (ierror == 0)
    {
      ierror = listen(sockfd, 5 );
      if (ierror != -1)
	{
	  while( TRUE )
	    {
#ifdef LINUX
	      socklen_t clilen;
#else
	      int clilen;
#endif
	      int newsockfd;
	      pid_t iChildPid;
	      SINT4 iConnectionNumber;
	      struct sockaddr_in cliaddr;

	      clilen = sizeof(cliaddr);
	      newsockfd = accept(sockfd, (struct sockaddr *)(void*)&cliaddr, &clilen);
	      if (newsockfd != -1)
		{

		  iConnectionNumber = ConnectionTester_a(&cliaddr);
		  if (iConnectionNumber>=0)
		    {
		      iChildPid = fork();
		      switch(iChildPid)
			{
			case -1:
			  /* Error in fork */
			  sig_signal_va( USER_TEST_NUMBER, 1, 1001 );
			  sig_signal( errno_to_message(errno) );
			  break;
			  
			case 0:
			  /* We are the child process, note that starting here we
			   * have fixed state from the parent process.
			   */
			  
			  /* Close the listen socket */
			  ierror = close( sockfd );
			  if (ierror == -1)
			    {
			      sig_signal( errno_to_message_va( errno, 1, "Child(close0())" ) ) ;
			    }
			  
			  ConnectionHandler_a( newsockfd, &cliaddr, iConnectionNumber );
			  
			  ierror = close( newsockfd );
			  if (ierror == -1)
			    {
			      sig_signal( errno_to_message_va( errno, 1, "Child(close1())" ) ) ;
			    }
			  exit(0);
			  break;
			  
			default:
			  /* We are the parent process.
			   */ 
			  ParentHandler_a( iChildPid, iConnectionNumber );
			  break;
			}
		    }
		  else
		    {
		      sig_signal_va( USER_NETMAX_EXCEEDED, 1, -iConnectionNumber );
		    }
		  /* Close the accept socket */
		  ierror = close( newsockfd );
		  if (ierror == -1)
		    {
		      sig_signal( errno_to_message_va( errno, 1, "Parent(close2())" ) ) ;
		    }
		}
	      else
		{
		  sig_signal( errno_to_message_va( errno, 1, "SocketListen()" ) ) ;
		}
	    } 


	}
      else
	{
	  sig_signal( errno_to_message_va( errno, 1, "SocketListen()" ) );
	}
    }
  else
    {
      ierror = errno;
      if (ierror == EADDRINUSE)
	{
	  laddr_in_use = TRUE;
	}
      else
	{
	  sig_signal( errno_to_message_va( errno, 1, "SocketListen(bind())" ) );
	}
    }

  /* Close the socket before returning */
  ierror = close( sockfd );
  if (ierror == -1)
    {
      sig_signal( errno_to_message_va( errno, 1, "SocketListen(close())" ) );
    }

  /* If we got the address in use error for the first time, latch the time.
   * If it has been more than 10 minutes, then signal a message.  Try to do
   * this only once.
   */
  if (laddr_in_use)
    {
      if (last_addr_in_use)
	{
	  if (!lsignalled_already)
	    {
	      UINT4 icurrent_time, itime_diff;
	      getbtime( &icurrent_time );
	      itime_diff = btimeGrace(icurrent_time - ifirst_time) / 60000 ;
	      if (itime_diff >= 10)
		{
		  sig_signal_va( USER_NETPORT_INUSE, 1, itime_diff );
		  lsignalled_already=TRUE;
		}
	    }

	}
      else
	{
	  getbtime( &ifirst_time );
	  lsignalled_already = FALSE;
	}

    }
  last_addr_in_use = laddr_in_use;
  return;

}
static UINT4 iEventFlag_c;
/* =================================================================
 * Socket listener with no blocking.
 */
void SocketListenNoBlock
(UINT2 iPort_a,             /* Port number to listen on */
 UINT4 iEventFlag_a,
 ContinueTestProc      lContinueTester_a,  /* Function to detect if we should return */
 ConnectionTestProc    iConnectionTester_a,
 ConnectionHandlerProc ConnectionHandler_a,
 ParentHandlerProc     ParentHandler_a
 )
{
  int sockfd, ierror;
  struct sockaddr_in servaddr;
  struct sigaction action;
  UINT2 laddr_in_use;
  static UINT2 last_addr_in_use = FALSE; /*last call got the error*/
  static UINT2 lsignalled_already;       /*Error was already signalled*/
  static UINT4 ifirst_time;
  UINT4 icurrent_time, itime_diff;
  UINT2 laccept_would_block;

  /* ------------------------------------------ */

  /* Store away the event flag for the signal handler */
  iEventFlag_c = iEventFlag_a;

  laddr_in_use = FALSE;
  sockfd = socket(AF_INET,SOCK_STREAM,0);
  if (sockfd == -1)
    {
      sig_signal( errno_to_message_va( errno, 1, "socket()" ) ) ;
      last_addr_in_use = laddr_in_use;
      return;
    }

  /* Set up a signal handler for the SIGIO signal */
  action.sa_handler = SocketSigioHandler;
  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, "sigaction()" ) ) ;

  /* allow the process to receive SIGIO */
  {
    pid_t iPid=getpid();
    ierror = fcntl(sockfd, F_SETOWN, iPid );
    if (ierror == -1) sig_signal( errno_to_message_va( errno, 1, "fcntl(F_SETOWN)" ) );
  }
#ifdef LINUX
  ierror = fcntl(sockfd, F_SETFL, FASYNC);
  if (ierror == -1) sig_signal( errno_to_message_va( errno, 1, "fcntl(F_SETFL)" ) );
#else
  {
    int iflag = 1;
    ierror = ioctl( sockfd, FIOASYNC, &iflag );
    if (ierror == -1) sig_signal( errno_to_message_va( errno, 1, "ioctl(FIOASYNC)" ) );
  }
#endif 

  {
    int iflag = 1;
    ierror = ioctl( sockfd, FIONBIO, &iflag );
    if (ierror == -1) sig_signal( errno_to_message_va( errno, 1, "ioctl(FIONBIO)" ) );
  }

  /* We now have a valid opened socket, it must be closed if we exit */

  memset( &servaddr, 0, sizeof(servaddr));
  servaddr.sin_family = AF_INET;
  servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
  servaddr.sin_port = htons(iPort_a);
  ierror = bind(sockfd, (struct sockaddr *)(void*)&servaddr, sizeof(servaddr));
  if (ierror == 0)
    {
      ierror = listen(sockfd, 5);
      if (ierror != -1)
	{

	  sig_waitef( iEventFlag_a );
	  sig_clref( iEventFlag_a );
	  while( lContinueTester_a() )
	    {
#ifdef LINUX
	      socklen_t clilen;
#else
	      int clilen;
#endif
	      int newsockfd;
	      struct sockaddr_in cliaddr;

	      clilen = sizeof(cliaddr);
	      newsockfd = accept(sockfd, (struct sockaddr *)(void*)&cliaddr, &clilen);
	      if (newsockfd != -1)
		{
		  pid_t iChildPid;
		  int iConnectionNumber;

		  laccept_would_block = FALSE;
		  iConnectionNumber = iConnectionTester_a(&cliaddr);
		  if (iConnectionNumber>=0)
		    {
		      iChildPid = fork();
		      switch(iChildPid)
			{
			case -1:
			  /* Error in fork */
			  sig_signal_va( USER_TEST_NUMBER, 1, 1001 );
			  sig_signal( errno_to_message(errno) );
			  break;

			case 0:
			  /* We are the child process */
			  ierror = close( sockfd );
			  if (ierror == -1)
			    {
			      sig_signal( errno_to_message_va
					  ( errno, 1, "Child(close0())" ) ) ;
			    }
			  
			  ConnectionHandler_a( newsockfd, &cliaddr, iConnectionNumber );

			  ierror = close( newsockfd );
			  if (ierror == -1)
			    {
			      sig_signal( errno_to_message_va
					  ( errno, 1, "Child(close1())" ) ) ;
			    }
			  exit(0);
			  break;

			default:
			  /* We are the parent process */
			  ParentHandler_a( iChildPid, iConnectionNumber );
			  break;

			}
		    }
		  else
		    {
		      sig_signal_va( USER_NETMAX_EXCEEDED, 1, -iConnectionNumber );
		    }
		  ierror = close( newsockfd );
		  if (ierror == -1)
		    {
		      sig_signal( errno_to_message_va
				  ( errno, 1, "Parent(close2())" ) ) ;
		    }
		}
	      else
		{
		  laccept_would_block = TRUE;
		  if (errno != EWOULDBLOCK)
		    {
		      sig_signal( errno_to_message_va( errno, 1, "accept()" ) ) ;
		    }
		}
	      if ( laccept_would_block )
		{
		  sig_waitef( iEventFlag_a );
		  sig_clref( iEventFlag_a );
		}
	    }
	}
      else
	{
	  sig_signal( errno_to_message_va( errno, 1, "listen()" ) );
	  sig_signal_va( USER_TEST_NUMBER, 1, 1005 );
	}
    }
  else
    {
      ierror = errno;
      if (ierror == EADDRINUSE)
	{
	  laddr_in_use = TRUE;
	}
      else
	{
	  sig_signal( errno_to_message_va( errno, 1, "bind()" ) );
	}
    }

  /* Close the socket before returning */
  ierror = close( sockfd );
  if (ierror == -1)
    {
      sig_signal( errno_to_message_va( errno, 1, "Parent(close3())" ) );
      sig_signal_va( USER_TEST_NUMBER, 1, 1007 );
    }

  /* If we got the address in use error for the first time, latch the time.
   * If it has been more than 10 minutes, then signal a message.  Try to do
   * this only once.
   */
  if (laddr_in_use)
    {
      if (last_addr_in_use)
	{
	  if (!lsignalled_already)
	    {
	      getbtime( &icurrent_time );
	      itime_diff = btimeGrace(icurrent_time - ifirst_time) / 60000 ;
	      if (itime_diff >= 10)
		{
		  sig_signal_va( USER_NETPORT_INUSE, 1, itime_diff );
		  lsignalled_already = TRUE;
		}
	    }

	}
      else
	{
	  getbtime( &ifirst_time );
	  lsignalled_already = FALSE;
	}

    }
  last_addr_in_use = laddr_in_use;
  return;

}

/* ================================================================
 * Signal handler for SIGIO signals
 */
static void SocketSigioHandler( int signal_a )
{
  sig_setef( iEventFlag_c );
  return;
}

/* ===========================================
 * Print string to log and terminal, if desired.
 */
void LoggingPrintf
( const struct sig_logging_spec *pLogging_a,
  const char *sString_a )
{

  char sTimeBuf[10], sDateBuf[TIMENAME_SIZE];
  struct ymds_time Ymds;
  GetTimeYmds( &Ymds, TIME_FLG_LOCAL );

  
  shhmmss_r( Ymds.isec, sTimeBuf );
  sddmonyyyy_r( Ymds.iyear, Ymds.imon, Ymds.iday, sDateBuf );
  
  if (pLogging_a)
    {
      if (pLogging_a->lLogToTerm) 
	{
	  fprintf( stderr, "%s %s.%03d:", sDateBuf, sTimeBuf, MS_FROM_MILLS(Ymds.imills) );
	  fputs( sString_a, stderr );
	}
      if (pLogging_a->lLogToFile)
	{
	  fprintf( pLogging_a->iLogFile, "%s %s.%03d:", sDateBuf, sTimeBuf, 
		   MS_FROM_MILLS(Ymds.imills) );
	  fputs( sString_a, pLogging_a->iLogFile );
	  fflush( pLogging_a->iLogFile );
	}
    }
  else
    {
      fprintf( stderr, "%s %s.%03d:", sDateBuf, sTimeBuf, MS_FROM_MILLS(Ymds.imills) );
      fputs( sString_a, stderr );
    }

  return;
}
