/*  File: acesocketlib.c
 *  Author: Ed Griffith (edgrif@sanger.ac.uk) & Jean Thierry-Mieg (mieg@crbm.cnrs-mop.fr)
 *  Copyright (c) J Thierry-Mieg and R Durbin, 1999
 *-------------------------------------------------------------------
 * This file is part of the ACEDB genome database package, written by
 * 	Richard Durbin (Sanger Centre, UK) rd@sanger.ac.uk, and
 *	Jean Thierry-Mieg (CRBM du CNRS, France) mieg@kaa.crbm.cnrs-mop.fr
 *
 * Description: Utility routines for the ace socket layer implementation.
 * Exported functions: See acesocket_.h
 * HISTORY:
 * Last edited: Mar 10 11:14 2004 (edgrif)
 * Created: Sat Jul 10 10:37:21 1999 (edgrif)
 * CVS info:   $Id: acesocketlib.c,v 1.17 2004/03/22 14:22:52 edgrif Exp $
 *-------------------------------------------------------------------
 */

#include <wh/regular.h>
#include <wsocket/acesocket_.h>


/* Internal routines.                                                        */
static S_MSGState readSocket(int fd, char *readbuf, int *bytesPending) ;
static S_MSGState writeSocket(int fd, char *writebuf, int *bytesPending) ;


/**************************************************************/

/* This is for IPv4, some work needed to make it independent need to get     */
/* quite a lot of stephens code to make it protocol independent...           */
/* bad, bad, bad...need IPv6...as well...                                    */
/*                                                                           */
BOOL getClientInetAddressInfo(ClientConnect connect_struct,
			      char **dotted_name, char **host_name)
{
  BOOL name_found = FALSE ;
  struct sockaddr_in *cliaddr = (struct sockaddr_in *)&(connect_struct->socket) ;
  struct hostent *host_entry = NULL ;
  char *client_dotted_decimal = NULL ;

  /* Get dotted decimal name, this call cannot fail.                         */
  client_dotted_decimal = inet_ntoa(cliaddr->sin_addr) ;

  /* Now get the string form of the host name, which can fail.               */
  host_entry = gethostbyaddr((const char *)&(cliaddr->sin_addr), sizeof(struct in_addr),
			     cliaddr->sin_family) ;
  if (host_entry)
    {
      *host_name = strnew(host_entry->h_name, 0) ;
      *dotted_name = strnew(client_dotted_decimal, 0) ;
      name_found = TRUE ;
    }
  else
    {
      messdump("Lookup of client hostname translation from binary connection number failed, "
	       "dotted decimal host address of client was %s, "
	       "and reason was: %s",
	       client_dotted_decimal, messErrnoText(h_errno)) ;
    }

  return name_found ;
}



/************************************************************/

/* Return TRUE if we were started by inetd, FALSE otherwise. The function    */
/* only checks this the first time through, subsequent calls then just get   */
/* the stored boolean.                                                       */
/*                                                                           */
/* If we were started by inetd then file descriptors 0, 1 and 2 must all be  */
/* the same internet socket.                                                 */
/* This code determines this for file descriptors 0 to 2 by:                 */
/*                                                                           */
/*   1) checking that they are all sockets                                   */
/*   2) checking that the sockets are internet (AF_INET) sockets             */
/*   3) checking that all three refer to the same socket address/port number */
/*                                                                           */
/* Don't know if you can do much more than that.                             */
/*                                                                           */
/* Optionally we return the port number so that this knowledge is all        */
/* encapsulated in this routine.                                             */
/*                                                                           */
/* Note: on windows we don't support inetd so we always return FALSE here.   */
BOOL isInetdDaemon(int *port_out)
{
#ifdef __CYGWIN__
  return FALSE;
#else
  static BOOL first_time = TRUE ;
  static BOOL inetdStarted = FALSE ;
  static unsigned short port ;
  int fd ;
  struct sockaddr_in sockaddr ;
  int sockaddrlen ;
  unsigned long  addr ;


  if (first_time)
    {
      first_time = FALSE ;

      for (fd = 0, inetdStarted = TRUE ; fd <= 2 && inetdStarted == TRUE ; fd++)
	{
	  sockaddrlen = sizeof sockaddr;

	  /* A better call to use here would be isfdtype() which Posix.1g    */
	  /* defines should be in sys/stat.h, but most of our systems don't  */
	  /* have this yet...sigh... (see p.81 in Stephens Network Vol 1)    */
	  if (getsockname(fd, (struct sockaddr *) &sockaddr, &sockaddrlen) == -1)
	    {
#ifdef SOLARIS
	      /* for some reason Solaris 5.5 returns EINVAL, this disagrees  */
	      /* with the manpage...sigh. Maybe there is some problem on     */
	      /* solaris with doing a getsockname on a listening socket...   */
	      if (errno == EINVAL)
		inetdStarted = FALSE ;
	      else
#endif
	      if ((errno == EBADF) || (errno == ENOTSOCK)
		  || (errno == EOPNOTSUPP) || (errno == ENOENT))
		inetdStarted = FALSE ;
	      else
		  messcrash("Cannot determine if server started by inetd "
			    "because call to getsockname() failed: %s", messSysErrorText()) ;
	    }

	  if (inetdStarted)
	    {
	      if (sockaddr.sin_family != AF_INET)
		inetdStarted = FALSE ;
	    }

	  if (inetdStarted)
	    {
	      if (fd == 0)
		{
		  addr = ntohl(sockaddr.sin_addr.s_addr);
		  port = ntohs(sockaddr.sin_port);
		}
	      else
		{
		  if ((addr != ntohl(sockaddr.sin_addr.s_addr)) ||
		      (port != ntohs(sockaddr.sin_port))) 
		    {
		      inetdStarted = FALSE ;
		    }
		}
	    }
	}
    }

  /* return the listening port number for the daemon.                        */
  if (inetdStarted && port_out != NULL)
    *port_out = port ;

  return inetdStarted ;

#endif /* !__CYGWIN__ */
}
/************************************************************/
/* little utility to mimick a genuine server reply */

void directReply (S_MESSAGE message, char *text)
{
  Stack s = stackCreate (strlen(text)+1) ;
  pushText (s, text) ;  
  stackDestroy (message->writeStack) ;
  message->writeStack = s ;
  message->isNew = TRUE ;
}


/**************************************************************/

static void buf2hdr(char *buf, ACE_HEADER hh)
{
  memcpy(&(hh->swapMagic), buf, 4) ; buf += 4 ;
  memcpy(&(hh->length), buf, 4) ;  buf += 4 ;
  memcpy(&(hh->serverVersion), buf, 4) ; buf += 4 ;
  memcpy(&(hh->clientId), buf, 4) ; buf += 4 ;
  memcpy(&(hh->maxBytes), buf, 4) ; buf += 4 ;
  memcpy(&(hh->msgType), buf, ACESERV_MSGTYPE_BUFLEN) ;
}

/**************************************************************/

static void hdr2buf(ACE_HEADER hh, char *buf)
{
  memcpy(buf, &(hh->swapMagic), 4) ; buf += 4 ;
  memcpy(buf, &(hh->length), 4) ;    buf += 4 ;
  memcpy(buf, &(hh->serverVersion), 4) ; buf += 4 ;
  memcpy(buf, &(hh->clientId), 4) ; buf += 4 ;
  memcpy(buf, &(hh->maxBytes), 4) ; buf += 4 ;
  memcpy(buf, &(hh->msgType), ACESERV_MSGTYPE_BUFLEN) ;

}

/**************************************************************/

static void bufferDoSwap (char *buf)
{
  int ii , nn = ACE_HEADER_SWAPFIELDS ;
  int *ip = (int*) buf ;
  char *cp, *cq ;
  
  /* First int in buf is the magic tag which must not be swapped so skip it. */  
  /* Swop all the rest.                                                      */
  nn-- ;
  while (ip++, nn--)
    {
      ii = *ip ;
      cp = (char *)(&ii) ;
      cq = (char*)ip ;
      *cq = *(cp+3) ;
      *(cq+1) = *(cp+2) ;
      *(cq+2) = *(cp+1) ;
      *(cq+3) = *(cp) ;
    }

  return ;
}

/**************************************************************/

static BOOL bufferToHeader (char *buf, ACE_HEADER hh)
{
  int magic = *(int*)buf ;

  switch (magic)
    {
    case OK_MAGIC:
      break ;
    case SWAP_MAGIC:
      bufferDoSwap(buf) ;
      break ;
    default:
      return FALSE ;
    }
  buf2hdr(buf, hh) ;

  return TRUE ;
}

/**************************************************************/
static BOOL headerToBuffer (ACE_HEADER hh, char *buf)
{
  int magic = hh->swapMagic ;

  hdr2buf(hh, buf) ;

  switch (magic)
    {
    case SWAP_MAGIC:
      bufferDoSwap(buf) ;
      break ;
    case OK_MAGIC:
      break ;
    default:
      return FALSE ;
    }
  return TRUE ;
}


/**************************************************************/

int Fcntl(int fd, int cmd, int arg)
{
  int n = fcntl (fd, cmd, arg);
	
  if (n == -1)
    messcrash("fcntl error setting blocking state of socket, %s", messSysErrorText());

  return n ;
}

/**************************************************************/

void Connect(int fd, const struct sockaddr *sa, socklen_t salen)
{
  if (connect(fd, sa, salen) < 0)
    messcrash ("connect error %s", messSysErrorText());
}

/**************************************************************/

/* select may fail for a number of reasons, if we get a signal the select is interrupted
 * and returns with n < 0. If the signal was SIGTERM, the acedb signal handler will have
 * called our sigterm routine to set the acesock_sigterm_received_G global meaning we must
 * return to allow acedb to save and exit. */
int aceSocketSelect(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,
	   /* struct timeval */ void  *timeout)
{
  int n;

  restart:
  n = select(nfds, readfds, writefds, exceptfds, timeout) ;
  
  if (n < 0)
    { 
      if (errno == EINTR)
	{
	  if (!acesock_sigterm_received_G)		    /* i.e. just return for sigterm. */
	    goto restart;
	}
      else
	messcrash("Cannot select on socket %s", messSysErrorText()) ; 
    }

  return n ;		/* can return 0 on timeout */
}

/**************************************************************/

/*
 *                Functions for closing sockets.
 */


/* This call will cause TCP to close the socket immediately and will then close
 * the file descriptor with a call to close(). */
void aceSocketClose(int fd)
{
  /* Ignore return code from this as we always want to close afterwards. */
  Shutdown(fd, SHUT_RDWR) ;

  Close(fd) ;

  return ;
}


/* Always does a TCP close of the socket but does not get rid of the file
 * handle, need to call close() for that.
 * We return TRUE if socket got closed and FALSE if its already disconnected.
 * Otherwise we crash because other errors from shutdown imply a coding error.
 */
BOOL Shutdown(int fd, int mode)
{
  BOOL result = FALSE ;

  if (shutdown(fd, mode) < 0)
    {
      /* ENOTCONN means socket could not be shutdown because it is already disconnected. */
      if (errno == ENOTCONN)
	result = FALSE ;
      else
	messcrash("Internal logic error, invalid option to shutdown call: %s",
		  messSysErrorText()) ;
    }
  else
    result = TRUE ;

  return result ;
}


/* Will close a processes file handle for the socket but may not terminate the
 * TCP socket if another process has it open.
 * Note we ignore the "Connection reset by peer" error, this implies that the client
 * died and then we tried to close the connection. */
void Close(int fd)
{
  if (close(fd) == -1 && errno != ECONNRESET)
    messcrash("Could not close socket connection, %s", messSysErrorText()) ;
}



/**************************************************************/
/* Create a blank message header.                                            */
S_MESSAGE sMessageCreate(void)
{
  S_MESSAGE msg= (S_MESSAGE) messalloc(sizeof(struct S_MESSAGESTRUCT)) ;

  /* everything is initialized to zero by messalloc */  
  return msg ;
}

/* Set up a message to receive data from a socket.                           */
void sMessageSetRead(S_MESSAGE msg)
{
  if (msg->isNew)
    {
      msg->doneHeader = FALSE ;
      msg->hBytesRequested =  msg->hBytesPending = ACE_HEADER_BYTES ;
      memset (msg->hBuffer,0, ACE_HEADER_BYTES) ;
      msg->mBytesRequested =  msg->mBytesPending = 0 ;
      messfree (msg->readBuffer) ;
      stackDestroy (msg->writeStack) ; 
      msg->isNew = FALSE ;
    }
  return ;
}


/* Set up a message to be written out to a socket.                           */
/*                                                                           */
void sMessageSetWrite(S_MESSAGE msg)
{
 if (msg->isNew)
    {
      msg->doneHeader = FALSE ;
      msg->hBytesRequested =  msg->hBytesPending = ACE_HEADER_BYTES ;
      memset (msg->hBuffer,0, ACE_HEADER_BYTES) ;

      /* You need to be aware that stackMark gives the length of memory      */
      /* allocated to the current item on the stack, but this can be larger  */
      /* than the item...there is padding allowed to get alignment for       */
      /* stuffing ints etc. on to the stack. This can mess up attempts to    */
      /* find the length of text etc., it means that you have to know that   */
      /* something on the stack is text and do your own strlen, stack only   */
      /* returns its own internal length, not the length you originally      */
      /* supplied...                                                         */
      msg->ah.length =
	msg->mBytesPending = 
	msg->mBytesRequested = stackMark(msg->writeStack) ;


#ifdef ED_G_NEVER_INCLUDE_THIS_CODE
      /* Ok, I did this because I couldn't get the above to work but I think */
      /* my problems were caused by not appreciating the below stuff about   */
      /* pushTest and catText which are used non-obviously in the server     */
      /* code.                                                               */

      /* The above code now seems to work.                                   */

      /*                   YOU HAVE TO MAKE SURE THAT AFTER THE FIRST "PUSH" */
      /* ON TO THE STACK, ONLY CATTEXT IS USED AFTER THIS, NOT SO EASY       */
      /* BECAUSE THE ACECOMMANDEXECUTE DOES A PUSHTEXT FOR SOME COMMANDS     */
      /* BUT (YOU GUESSED IT) NOT OTHERS...AGGGHHHH SO IF YOU DO A PUSHTEXT  */
      /* AS WELL IT ALL BREAKS... see the code in serverace.c where it       */
      /* returns text...basically it does only "catext" once we have done    */
      /* an acecommand execute...aggghhh....actually its worse than this     */
      /* because the aceOut code which is used by acecommandexecute does     */
      /* NOT do a cattext....it does a memcpy basically....                  */
      msg->ah.length =
	msg->mBytesPending = 
	msg->mBytesRequested = strlen(stackText(msg->writeStack, 0)) + 1 ;
#endif /* ED_G_NEVER_INCLUDE_THIS_CODE */


      messfree (msg->readBuffer) ;  
      msg->isNew = FALSE ;
    }
 return ;
}



/* Set the magic, the client should set this on its first write, the server  */
/* should then copy the clients magic and use it for subsequent writes to    */
/* the client.                                                               */
void sMessageSetMagic(S_MESSAGE msg)
{
  msg->ah.swapMagic = OK_MAGIC ;
}

int  sMessageGetMagic(S_MESSAGE msg)
{
  return msg->ah.swapMagic ;
}

/* We will need this, maybe we already do...not sure of the interface to     */
/* ace...it should extract the length and message from ace...                */
/* N.B. no safe guards at the moment, could be used before message is all    */
/* read...                                                                   */
int sMessageGetMessage(S_MESSAGE msg, char **msgtext)
{
  *(msgtext) = msg->readBuffer ;
  return (msg->mBytesRequested) ;
}


/* Clean up a message of allocated resources                                 */
void sMessageDestroy(S_MESSAGE msg)
{ 
  if (msg)
    {
      messfree (msg->readBuffer) ;  
      stackDestroy (msg->writeStack) ;  
      messfree(msg) ;
    }
  messfree(msg) ;
}

/**************************************************************/

/* n.b. It is possible to peek socket buffers to see what is in there, but   */
/* this method gives extra complications, what if the client crashes between */
/* peek and read ?? what if the whole message is to big to be transmitted in */
/* one go, it's just not worth the hassle, hence we do straight reads.       */
S_MSGState sMessageSocketRead(int fd, S_MESSAGE req)
{
  S_MSGState state = SMSG_ERROR ;
  int sockFlags ;

  /* We can't just do a recv with MSG_DONTWAIT and MSG_PEEK because          */
  /* the former is not supported on many systems yet. So instead we make the */
  /* whole socket non-blocking, do the recv and then reset the socket.       */
  sockFlags = Fcntl(fd, F_GETFL, 0) ;
  Fcntl(fd, F_SETFL, sockFlags | O_NONBLOCK) ;

  /* Try to read the header, probably do this in one go.                     */
  if (req->doneHeader == FALSE)
    {
      /* First time for this message so allocate a buffer.                   */
      if (!req->hBytesPending)
	{
	  req->hBytesPending = req->hBytesRequested = ACE_HEADER_BYTES ;
	}

      state = readSocket(fd, req->hBuffer + (req->hBytesRequested - req->hBytesPending),
			 &(req->hBytesPending)) ;

      if (state == SMSG_DONE)
	{
	  if (!bufferToHeader(req->hBuffer, &(req->ah)))
	    {
	      state = SMSG_ERROR ;
	      messout("Error in socket header format from client.") ;
	    }
	  else
	    req->doneHeader = TRUE ;
	}
    }

  /* We have a header so try to read buffer (this may take several calls to  */
  /* this function).                                                         */
  if (req->doneHeader == TRUE)
    {
      /* First time for this message so allocate a buffer.                   */
      if (!req->readBuffer)
	{
	  req->readBuffer = messalloc (req->ah.length) ;
	  req->mBytesRequested = req->mBytesPending = req->ah.length ;
	}

      state = readSocket(fd, req->readBuffer + req->mBytesRequested - req->mBytesPending,
			 &(req->mBytesPending)) ;
    }

  /* Reset the socket to blocking.                                           */
  Fcntl(fd, F_SETFL, sockFlags) ;

  return state ;
}


/* Common read routine for handling reading from a socket and any resulting  */
/* errors. Note that it updates the the number of bytes left to read given   */
/* by bytesPending.                                                          */
static S_MSGState readSocket(int fd, char *readbuf, int *bytesPending)
{
  S_MSGState state = SMSG_ERROR ;
  int bytes ;

  bytes = read(fd, readbuf, *bytesPending) ;
  if (bytes < 0)
    {
      /* This is potentially not strict enough, we may need to put a load of */
      /* #defines in here to check for SYS_V, POSIX etc. etc. to make sure   */
      /* we check for the correct errno's...aaaggghhh....                    */
      if (errno == EWOULDBLOCK || errno == EINTR || errno == EAGAIN)
	state = SMSG_WAIT ;
      else
	{
	  messdump("read error trying to read client data on socket,"
		   "connection terminated:  %s", messSysErrorText());
	  state = SMSG_ERROR ;
	}
    }
  else if (bytes == 0)
    {
      messdump("zero bytes returned on read, client has prematurely closed connection.") ;
      state = SMSG_ERROR ;
    }
  else
    {
      *bytesPending -= bytes ;
      
      if (*bytesPending < 0)
	{
	  messdump("read more bytes than number requested ") ;
	  state = SMSG_ERROR ;
	}
      else  if (*bytesPending == 0)
	state = SMSG_DONE ;
      else
	state = SMSG_WAIT ;
    }

  return state ;
}



/**************************************************************/

/* Jean I added code here to store any current handler for SIGPIPE, we don't */
/* know if the rest of the application will have installed one. We then      */
/* turn off signal handling for SIGPIPE so that we get EPIPE for a write to  */
/* a broken connection which we handle here. Then we turn back on any        */
/* existing signal handler.                                                  */
S_MSGState sMessageSocketWrite(int fd, S_MESSAGE req)
{
  S_MSGState state = SMSG_WAIT ;
  int sockFlags ;
  struct sigaction oursigpipe, oldsigpipe ;

  /* writes can deliver a SIGPIPE if the socket has been disconnected, by    */
  /* ignoring we will just receive errno = EPIPE.                            */
  oursigpipe.sa_handler = SIG_IGN ;
  sigemptyset(&oursigpipe.sa_mask) ;
  oursigpipe.sa_flags = 0 ;
  if (sigaction(SIGPIPE, &oursigpipe, &oldsigpipe) < 0)
    messcrash("Cannot set SIG_IGN for SIGPIPE for socket write operations") ;


  /* We can't just do a recv with MSG_DONTWAIT and MSG_PEEK because          */
  /* the former is not supported on many systems yet. So instead we make the */
  /* whole socket non-blocking, do the recv and then reset the socket.       */
  sockFlags = Fcntl(fd, F_GETFL, 0) ;
  Fcntl(fd, F_SETFL, sockFlags | O_NONBLOCK) ;


  /* OK, Let's try to write the header                                       */
  if (req->doneHeader == FALSE)
    {
      /* Do any byte swapping.                                               */
      headerToBuffer(&(req->ah), req->hBuffer) ;

      /* Now do the writes...                                                */
      state = writeSocket(fd, req->hBuffer + req->hBytesRequested - req->hBytesPending,
			  &(req->hBytesPending)) ;
      if (state == SMSG_DONE)
	req->doneHeader = TRUE ;
    }

  /* OK, header written, so try to write buffer.                             */
  if (req->doneHeader == TRUE)
    {
      if (req->mBytesPending <= 0)
	{
	  messdump("Logic error, caller is trying to write %d bytes to socket.", req->mBytesPending) ;
	  state = SMSG_ERROR ;
	}
      else
	{
	  state = writeSocket(fd,
			      stackText(req->writeStack, req->mBytesRequested - req->mBytesPending),
			      &(req->mBytesPending)) ;
	}
    }

  /* Reset the socket to blocking.                                           */
  Fcntl(fd, F_SETFL, sockFlags) ;

  /* Reset the old signal handler.                                           */
  if (sigaction(SIGPIPE, &oldsigpipe, NULL) < 0)
    messcrash("Cannot reset previous handler for signal SIGPIPE for socket write operations") ;

  return state ;
}


/* Common write routine for handling writing to a socket and any resulting   */
/* errors. Note that it updates the the number of bytes left to write given  */
/* by bytesPending.                                                          */
static S_MSGState writeSocket(int fd, char *writebuf, int *bytesPending)
{
  S_MSGState state = SMSG_ERROR ;
  int bytes ;

  bytes = write(fd, writebuf, *bytesPending) ;

  if (bytes < 0)
    {
      if (errno == EINTR || errno == EAGAIN)
	state = SMSG_WAIT ;
      else
	{
	  messdump("Cannot write message buffer to socket, %s", messSysErrorText()) ;
	  state = SMSG_ERROR ;
	}
    }
  else
    {
      *bytesPending -= bytes ;

      if (*bytesPending < 0)
	{
	  messdump("wrote more bytes than number requested ") ;
	  state = SMSG_ERROR ;
	}
      else  if (*bytesPending == 0)
	state = SMSG_DONE ;
      else 
	state = SMSG_WAIT ;
    }

  return state ;
}


/**************************************************************/
/**************************************************************/
