/*  File: acesocket.c
 *  Author: Ed Griffith (edgrif@sanger.ac.uk) & Jean Thierry-Mieg (mieg@crbm.cnrs-mop.fr)
 *  Copyright (C) J Thierry-Mieg and R Durbin, 1992
 *-------------------------------------------------------------------
 * This file is part of the ACEDB genome database package, written by
 * 	Richard Durbin (MRC LMB, UK) rd@mrc-lmb.cam.ac.uk, and
 *	Jean Thierry-Mieg (CRBM du CNRS, France) mieg@kaa.cnrs-mop.fr
 *
 * Description: Implements the TCP Socket communication layer that
 *              sits between acedb and the network. Handles non-blocking
 *              connections between client(s) and server, handles
 *              timeouts, socket errors. Keeps clients as a circularly
 *              linked list with clients being maintained as finite
 *              state machines (reading, writing, etc.).
 *
 * Exported functions:
 * HISTORY:
 * Last edited: Oct 20 18:04 2003 (edgrif)
 * * Mar 16 11:30 2000 (edgrif): Fix bug in timeout code so timeout
 *              is _since_ last socket communication.
 * Created: Wed July 8 1999 (mieg)
 * CVS info:   $Id: acesocket.c,v 1.20 2003/10/20 17:07:23 edgrif Exp $
 *-------------------------------------------------------------------
 */

#include <sys/types.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <wh/regular.h>
#include <wsocket/acesocket_.h>

#ifndef SOL_TCP
#define SOL_TCP 6
#endif

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

static int  aceSocketCreate (int port) ;
static void setCleanEnv (void) ;
static int clientAccept (int fd, struct sockaddr *sa, socklen_t *salenptr) ;

static S_CLIENT sClientCreate (S_CLIENT* currentClientp) ;
static void sClientDestroy (S_CLIENT* currentClientp, S_CLIENT client) ;
static void closeAllClients (S_CLIENT* currentClientp, S_CLIENT shut_client,
			     fd_set allreads, fd_set allwrites, Stack shutDownMessage,
			     AceSocketServerRequestRoutine serverRequestFunc, void *serverData) ;
static BOOL timeOut(S_CLIENT client, int clientTimeOut, int *curr_timeout) ;
static void resetTime(S_CLIENT client) ;
static void checkServerReturn(AceSocketRequestTarget currTarget, AceSocketRequestType currRequest) ;


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

/* Global data, if you add any more you should put all this in a struct
 * and add accessor functions etc. */

BOOL acesock_sigterm_received_G = FALSE ;		    /* Used for signal handling. */


/* local statics, please don't add any more unless you _absolutely_ have to. */
static BOOL debug_G = FALSE ;



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

void aceSocketListen (int port, int serverTimeOut_in, int clientTimeOut_in,
		      AceSocketServerRequestRoutine serverRequestFunc, void *serverData) 
{
  int		maxfd, connfd ;
  int		nready ;
  fd_set	rset, wset, allreads, allwrites ;
  socklen_t	clilen;
  struct sockaddr_in cliaddr ;
  S_CLIENT currentClient = NULL , client = NULL ;
  int listenfd ;
  int time_out, serverTimeOut, clientTimeOut, shortest_timeout ;
  struct timeval tv, *tv_ptr ;
  int sockFlags ;
  AceSocketRequestTarget currTarget = ACESOCK_NONE ;
  AceSocketRequestType currRequest = ACESOCK_NULL ;
  BOOL shuttingDown = FALSE ;
  AceSocketRequestType serverRequest = ACESOCK_NULL ;
  Stack shutMessage = NULL ;


  /* Set timeouts for clients and the server itself.                         */
  /* If either is set to the special value zero, this implies an infinite    */
  /* timeout which should only be used for testing.                          */
  serverTimeOut = serverTimeOut_in >= ACESERV_MIN_TIMEOUT ? serverTimeOut_in : 600 ;
  clientTimeOut = clientTimeOut_in >= ACESERV_MIN_TIMEOUT ? clientTimeOut_in : 600 ;
  shortest_timeout = clientTimeOut ;

  /* Create a listening socket (non-blocking) on which to receive new clients*/
  listenfd = aceSocketCreate(port) ;
  sockFlags = Fcntl(listenfd, F_GETFL, 0) ;
  Fcntl(listenfd, F_SETFL, sockFlags | O_NONBLOCK) ;

  /* Set up the reading/writing socket sets for monitoring client connections*/
  maxfd = listenfd ;
  FD_ZERO(&allreads);
  FD_ZERO(&allwrites);
  FD_SET(listenfd, &allreads);

  /* Loop forever waiting for clients to connect and send requests, we exit  */
  /* when 'serverTimeOut' time has passed with no requests sent.             */
  while (TRUE)
    {

      /* Shutdown server/sockets in requested way.                           */
    shutdownCode:
      if (shuttingDown || acesock_sigterm_received_G)
	{
	  /* Forced shutdown so kill all clients.                            */
	  if (serverRequest == ACESOCK_SHUTFORCE
	      || serverRequest == ACESOCK_TIMEDOUT)
	    {
	      closeAllClients(&currentClient, client, allreads, allwrites, shutMessage,
			      serverRequestFunc, serverData) ;
	    }

	  /* Once all clients are gone then do the shutdown.                 */
	  if (serverRequest == ACESOCK_SHUTFORCE
	      || serverRequest == ACESOCK_TIMEDOUT
	      || (!currentClient && serverRequest == ACESOCK_SHUTQUIESCE)
	      || serverRequest == ACESOCK_EXIT
	      || serverRequest == ACESOCK_TERM)
	    {	
	      /* Close listening socket and call server to do final exit.    */
	      /* n.b. only close the listening socket if we are _not_ inetd  */
	      /* started. Probably we could just always do a close because   */
	      /* it only closes the socket if the use count is 0.            */
	      if (!isInetdDaemon(NULL))
		Close(listenfd) ;
	      currTarget = ACESOCK_SERVER ;
	      if (serverRequest == ACESOCK_TERM)
		currRequest = ACESOCK_TERM ;
	      else
		currRequest = ACESOCK_EXIT ;
	      serverRequestFunc(serverData, client, 0, &currTarget, &currRequest, NULL, NULL) ;
	    }
	}


      /* Currently we only listen for reading or writing sockets to become   */
      /* ready....                                                           */
      /* ONE DAY WE MAY WISH TO LISTEN FOR EXCEPTIONS AS WELL...             */
      rset = allreads ;
      wset = allwrites ;

      /* Set the time out for the select call:                               */
      /*      no clients => use server time out                              */
      /*          client => use shortest time to time out a client           */
      /* If server or client time outs are zero, then servers/clients never  */
      /* time out (good for testing).                                        */
      if (currentClient == NULL)
	time_out = serverTimeOut ;
      else
	{
	  if (clientTimeOut == ACESERV_INFINITE_TIMEOUT)
	    time_out = clientTimeOut ;
	  else
	    time_out = shortest_timeout ;
	}

      /* Setting tv_ptr to NULL means "never time out", setting time_out to  */
      /* zero causes the select to return immediately.                       */
      if ((currentClient == NULL && serverTimeOut == 0)
	  || (currentClient != NULL && clientTimeOut == 0))
	tv_ptr = NULL ;
      else
	{
	  tv.tv_sec = time_out ;			    /* LINUX systems modify tv param. */
	  tv.tv_usec = 0 ;
	  tv_ptr = &tv ;
	}

      /* Block waiting for a socket to become ready, or we time out.         */
      nready = aceSocketSelect(maxfd+1, &rset, &wset, NULL, tv_ptr) ;
      if (nready == 0 || acesock_sigterm_received_G)
	{
	  if (acesock_sigterm_received_G)
	    {
	      /* Somewhere we received a sigterm so exit asap */
	      serverRequest = ACESOCK_TERM ;
	    }
	  else if (currentClient == NULL)
	    {
	      /* If there are no clients then the server has timed out and we    */
	      /* shutdown, otherwise we fall through to time out clients.        */

	      /* Call server to alert to timeout                                 */
	      currTarget = ACESOCK_SERVER, currRequest = ACESOCK_TIMEDOUT ;
	      shutMessage = serverRequestFunc(serverData, client, 0, &currTarget, &currRequest,
					      NULL, NULL) ;
	      /* We could allow server to cancel timeout by returning NULL       */
	      /* request...currently timeout => shutdown.                        */
	      shuttingDown = TRUE ;
	      serverRequest = ACESOCK_TIMEDOUT ;
	    }

	  goto shutdownCode ;
	}



      /* Is there a new client connected to the listening descriptor ? Note  */
      /* that accept CAN fail even if select says there is a new client,     */
      /* maybe because client aborted, in this case we ignore the connect.   */
      /* We can only have up to (FD_SETSIZE - 1) sockets, if the new socket  */
      /* would exceed this, we accept the connection, log the problem and    */
      /* close the connection in an orderly way. Currently the unlucky user  */
      /* will get no message from us but their client should tell them.      */
      if (FD_ISSET(listenfd, &rset))
	{
	  clilen = sizeof(cliaddr);

	  /* connfd may get interrupted and so may not return a valid fd.    */
	  connfd = clientAccept(listenfd, (struct sockaddr *)&cliaddr, &clilen) ;
	  if (connfd >= 0)
	    {
	      /* uugghh, wrong place, we don't even need to accept the       */
	      /* above connection, I guess I put it here because I was going */
	      /* to send an error message.                                   */
	      if (shuttingDown)
		{
		  /* No message, we don't know what state server is in so no */
		  /* point in calling it.                                    */
		  messdump("Client attempting to connect but server is closing down "
			   "so connection refused.") ;

		  aceSocketClose(connfd) ;
		}
	      else if(connfd >= FD_SETSIZE)
		{
		  /* Oops, too many clients (FD_SETSIZE is usually 256). */
		  /* Currently, unlucky connection is just terminated.   */
		  messdump("Refused connection from client because it would have "
			   "exceeded the maximum number of files/sockets"
			   "allowed by the 'select' call (FD_SETSIZE), which is  %d",
			   FD_SETSIZE) ; 

		  aceSocketClose(connfd) ;
		}

#ifdef ED_G_NEVER_INCLUDE_THIS_CODE
	      else if (!setClientInetAddressInfo(&cliaddr, &dotted_name, &host_name))
		{
		  messdump("Refused connection from client because could not "
			   "resolve its hostname.") ;

#ifdef ED_G_NEVER_INCLUDE_THIS_CODE
		  Shutdown(connfd, SHUT_RDWR) ;
#endif /* ED_G_NEVER_INCLUDE_THIS_CODE */

		  aceSocketClose(connfd) ;

		}
#endif /* ED_G_NEVER_INCLUDE_THIS_CODE */

	      else
		{
		  int one = 1;
		  /* Accept the connection and make a new client.        */
	 	  if (setsockopt(connfd, SOL_TCP, TCP_NODELAY, &one, sizeof(one)) < 0)
		    messdump("new client on socket %d - cannot set TCP_NODELAY: errno %d\n",
		      connfd, errno);

		  client = sClientCreate (&currentClient) ;
		  client->state = CLCON_CONN ;
		  client->fd = connfd ;
		  client->socket = cliaddr ;		    /* struct copy */
		  
		  /* Set up select call params to read from new clients sock.*/
		  if (connfd > maxfd)
		    maxfd = connfd;			    /* needed by select call */
		  FD_SET(client->fd, &allreads);	    /* new clients are always in read set. */
		}
 	    }
	}
      
      /* No clients and client connect failed so just select again.          */
      if (!currentClient)
	continue ;

      /* OK, process some clients...                                         */
      /* We always try to start our round with the next client in the        */
      /* circular list to attempt some fairness in dispatching.              */
      client = currentClient = currentClient->next ;

      /* We need to find out the client with the shortest time until it times*/
      /* out so we reset the curr client time out and check it against each  */
      /* client.                                                             */
      shortest_timeout = clientTimeOut ;

      /* Cycle through all the clients once, clients may move through several*/
      /* states in one "turn", e.g. read->error->close, both to ensure some  */
      /* events are processed in a timely manner _and_ to avoid getting stuck*/
      /* in a state not reliant on a read or write to a socket, e.g. request.*/
      do
	{

	lao:

	  /* If client is in one of the states associated with a socket then */
	  /* check for timeout (we don't check for the others as they happen */
	  /* synchronously). If client has not timed out AND its socket is   */
	  /* ready (for reading or writing) then reset its timer.            */
	  if (client->state == CLCON_CONN || client->state == CLCON_READWAIT
	      || client->state == CLCON_WRITEWAIT)
	    {
	      if (timeOut(client, clientTimeOut, &shortest_timeout))
		{
		  client->state = CLCON_CLOSE ;
		  client->close = CL_TIMEDOUT ; 
		}
	      else if (FD_ISSET(client->fd, &rset) || FD_ISSET(client->fd, &wset))
		{
		  resetTime(client) ;
		}
	    }


	  switch (client->state)
	    {
	    case CLCON_CONN:
	      if (FD_ISSET(client->fd, &rset))		    /* New data from client. */
		{
		  client->state = CLCON_READWAIT ;
		  client->message->isNew = TRUE ;
		  goto lao ;
		}
	      break ;
	    case CLCON_READWAIT:
	      if (FD_ISSET(client->fd, &rset))		    /* New/more data from client. */
		{
		  S_MSGState msgState ;

		  if (client->message->isNew)
		    sMessageSetRead(client->message) ;

		  msgState = sMessageSocketRead(client->fd, client->message) ;
		  if (msgState == SMSG_WAIT)
		    {
		      client->state = CLCON_READWAIT ;
		    }
		  else if (msgState == SMSG_DONE)
		    {
		      client->state = CLCON_REQUEST ;
		      goto lao ;			    /* Process request immediately. */
		    }
		  else if (msgState == SMSG_ERROR)
		    {
		      client->state = CLCON_CLOSE ;
		      client->close = CL_ERROR ;
		      goto lao ;			    /* Process error immediately. */
		    }
		}
	      break ;
	    case CLCON_REQUEST:
	      /* Send a request to the server code and get its reply to send */
	      /* back to the client.                                         */
	      stackDestroy (client->message->writeStack) ;
	      currTarget = ACESOCK_CLIENT, currRequest = ACESOCK_REQ ;

	      client->message->writeStack = serverRequestFunc(serverData,
							      client, client->fd,
							      &currTarget, &currRequest,
							      &(client->message->ah), 
							      client->message->readBuffer) ;
	      client->mustCallAce = TRUE ;		    /* Remember that we have called ace. */

	      /* Make sure that we get something sensible back, exit if not. */
	      checkServerReturn(currTarget, currRequest) ;

	      if (currTarget == ACESOCK_SERVER)
		{
		  /* Our interface shutdowns are generated at the request of */
		  /* this client.                                            */
		  if (currRequest == ACESOCK_SHUTFORCE)
		    {
		      /* Immediate kill of the server & all clients.         */
		      shuttingDown = TRUE ;
		      serverRequest = currRequest ;
		      if (client->message->writeStack)
			shutMessage = stackCopy(client->message->writeStack, 0) ;
		      goto shutdownCode ;		    /* N.B. the goto !! */
		    }
		  else if (currRequest == ACESOCK_SHUTQUIESCE)
		    {
		      /* Kill this client as it requested the shutdown and   */
		      /* then wait for all clients to quit.                  */
		      shuttingDown = TRUE ;
		      serverRequest = currRequest ;
		      client->mustCallAce = FALSE ;
		      client->state = CLCON_CLOSE ;
		      client->close = CL_NORMAL ;	
		      if (client->message->writeStack)
			  client->message->isNew = TRUE ;
		    }
		}
	      else
		{
		  if (currRequest == ACESOCK_REQ)
		    {
		      if (client->message->writeStack)
			{
			  /* a new message should be written */
			  client->state = CLCON_WRITEWAIT ;
			  client->message->isNew = TRUE ;
			  FD_CLR(client->fd, &allreads) ;
			  FD_SET(client->fd, &allwrites) ;
			}
		      else
			client->state = CLCON_CONN ;	    /* request with no reply from acedb. */
		    }
		  else if (currRequest == ACESOCK_KILL)
		    {
		      /* Server wishes to kill client so no need to call it  */
		      /* again.                                              */
		      client->mustCallAce = FALSE ;
		      client->state = CLCON_CLOSE ;
		      client->close = CL_NORMAL ;	
		      if (client->message->writeStack)
			  client->message->isNew = TRUE ;     /* a new message should be written */
		    }
		}
	      goto lao ;
	      break ;
	    case  CLCON_WRITEWAIT:
	      if (FD_ISSET(client->fd, &wset))
		{
		  S_MSGState msgState ;

		  if (client->message->isNew)
		    sMessageSetWrite(client->message) ;
		  msgState = sMessageSocketWrite(client->fd, client->message) ;
		  if (msgState == SMSG_WAIT)
		    {
		      client->state = CLCON_WRITEWAIT ;
		    }
		  else if (msgState == SMSG_DONE)
		    {
		      client->state = CLCON_CONN ;
		      FD_CLR(client->fd, &allwrites) ;	    /* Swop from write to read set. */
		      FD_SET(client->fd, &allreads) ;
		    }
		  else if (msgState == SMSG_ERROR)
		    {
		      client->state = CLCON_CLOSE ;
		      client->close = CL_ERROR ;  
		      goto lao ;
		    }
		}
	      break ;
	    case CLCON_CLOSE:
	      /* We do all closes immediately after the above operations other-  */
	      /* wise we may block on the select and not close the client for    */
	      /* a long time.                                                    */

	      /* Note that mustCallAce is TRUE only for timedout and error   */
	      /* closes. normal close => we have already called ace.         */
	      if (client->mustCallAce)
		{
		  /* If client is being closed by the socket layer, call ace */
		  /* server so it can free up its client resources.          */
		  currTarget = ACESOCK_CLIENT ;
		  if (client->close == CL_TIMEDOUT)
		      currRequest = ACESOCK_TIMEDOUT ;
		  else if (client->close == CL_ERROR)
		    currRequest = ACESOCK_KILL ;
		  client->message->writeStack =
		    serverRequestFunc(serverData,
				      client, client->fd,
				      &currTarget, &currRequest, &(client->message->ah), NULL) ;
		  if (client->message->writeStack && client->close == CL_TIMEDOUT)
		    client->message->isNew = TRUE ;
		}
	      
	      /* If there's a termination message send it, but only to a     */
	      /* client with no socket errors.                               */
	      if (client->close != CL_ERROR && client->message->isNew)
		{
		  /* Just try to write once, don't even bother to check, perhaps */
		  /* a bit hard on a normal quitter.                             */
		  /* Could insert my code to try writing a number of times */
		  /* and then just stop...                               */
		  sMessageSetWrite(client->message) ;
		  sMessageSocketWrite(client->fd, client->message) ;
		}

	      FD_CLR(client->fd, &allreads) ;
	      FD_CLR(client->fd, &allwrites) ;

	      sClientDestroy(&currentClient, client) ;	    /* Does the close as well. */
	      client = NULL ;

	      if (currentClient == NULL)		    /* Last client in list ? */
		client = NULL ;
	      else
		client = currentClient ;

	      break ;

	    default:
	      /* Bad news, somehow assigned a client state we don't recognise*/
	      messcrash ("Internal logic error, client connection FSM is in an unknown state.") ;
	      break ;
	    }

	  if (client != NULL)				    /* Any clients left ?? */
	    client = client->next ;

	} while (client != currentClient) ;		    /* Loop through clients once */

    }  /* loop back on Select */


  messcrash("We should never reach the end of this function (aceSocketListen), "
	    "logic error in code.") ;
  return ;
}


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

void aceSocketDebug(BOOL debug_on)
{
  debug_G = debug_on ;

  return ;
}


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

/* Sets a global which we use to try to shut down everything cleanly in the
 * event of receiving a TERM signal. */
void aceTransportRequestShutdown(void)
{
  acesock_sigterm_received_G = TRUE ;
}


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

/* Only really called when we are run by inetd                               */
/* We try to clear any pending data on the listening socket, if we don't do  */
/* this then inetd will immediately start up the server again, which will    */
/* crash again only to be restarted again, etc.etc.                          */
/*                                                                           */
/* Note that the listening socket MUST NOT be closed here because this may   */
/* cause inetd to crash.                                                     */
/*                                                                           */
void aceSocketInetdCleanup(void)
{
  if (isInetdDaemon(NULL))
    {
      int nready, maxfd, connfd, listenfd ;
      fd_set rset ;
      socklen_t clilen;
      struct sockaddr_in cliaddr ;
      struct timeval tv ;

      /* BECAUSE its inetd, we _know_ the listening socket file desc. == 0   */
      listenfd = 0 ;

      maxfd = listenfd ;
      FD_ZERO(&rset) ;
      FD_SET(listenfd, &rset) ;
      tv.tv_sec = 0 ;					    /* Don't wait at all. */
      tv.tv_usec = 0 ;
      nready = aceSocketSelect(maxfd+1, &rset, NULL, NULL, &tv) ;
      if (nready == 0)
	{
	  messdump("Could not read listening socket even though there should be data"
		   "on it, suggests severe problem with inetd/listening socket") ;
	}
      else
	{
	  if (FD_ISSET(listenfd, &rset))		    /* redundant, must be true really. */
	    {
	      clilen = sizeof(cliaddr);

	      /* connfd may get interrupted and so may not return a valid fd.    */
	      connfd = clientAccept(listenfd, (struct sockaddr *)&cliaddr, &clilen) ;
	      if (connfd >= 0)
		{
		  Shutdown(connfd, SHUT_RDWR) ;
		}
	    }
	}

    }

  return ;
}



/************************************************************/
/***************  Internal routines  ************************/

/* Clients are organized as a circular doubly linked list
 * with currentClient pointing to zero (no clients in loop)
 * or to the currently active client in the list.
 */

static S_CLIENT sClientCreate (S_CLIENT* currentClientp)
{
  S_CLIENT client = (S_CLIENT) messalloc (sizeof(ClientConnectStruct)) ;

  if (!*currentClientp) /* Create a ring of a single client ! */
    {
      *currentClientp = client ;
      client->previous = client ;
      client->next = client ;
    }
  else   /* install the new client just under the current one */
    {
      client->next = (*currentClientp)->next ;
      client->next->previous = client ;
      (*currentClientp)->next = client ;
      client->previous = *currentClientp ;
    }

  /* Set client state to 'uninitialised'. */
  client->state = CLCON_EMPTY ;
  client->close = CL_NONE ;
  client->fd = -1 ;
  client->time = timeNow() ;
  client->message = sMessageCreate () ;
  client->mustCallAce = FALSE ;

  return client ;
}


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

static void sClientDestroy (S_CLIENT* currentClientp, S_CLIENT client) 
{
  client->previous->next = client->next ;
  client->next->previous = client->previous ;

  if (*currentClientp == client)
    *currentClientp = (client->next != client) ? client->next : NULL ;

  aceSocketClose(client->fd) ;

  sMessageDestroy (client->message) ;
  messfree (client) ;
}


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

/* This is basically a pretty crap interface, the first client is the one    */
/* that sent the shutdown request so we don't call ace to close that one.    */
/* Hardly transparent, there is much common code in the close client stuff   */
/* that could be unified...                                                  */
static void closeAllClients (S_CLIENT* currentClientp, S_CLIENT shut_client,
			     fd_set allreads, fd_set allwrites, Stack shutMessage,
			     AceSocketServerRequestRoutine serverRequestFunc, void *serverData)
{
  S_CLIENT client;
  AceSocketRequestTarget currTarget =  ACESOCK_CLIENT ;
  AceSocketRequestType currRequest = ACESOCK_KILL ;

  while ((client = *currentClientp))
    {
      /* Call server request func to close all clients....but not the   */
      /* one that sent the shutdown...                                       */
      if (client != shut_client)
	{
	  client->message->writeStack =
	    serverRequestFunc(serverData, client, client->fd,
			      &currTarget, &currRequest, &(client->message->ah), NULL) ;
	}
      
      /* Note that we ignore the message returned by the call to the server  */
      /* and just output our own.                                            */
      if (shutMessage)
	{
	  stackDestroy (client->message->writeStack) ;
	  client->message->writeStack = stackCopy(shutMessage, 0) ;
	  client->message->isNew = TRUE ;
	  sMessageSetWrite(client->message) ;
	  sMessageSocketWrite(client->fd, client->message) ;
	}
      FD_CLR(client->fd, &allreads) ;
      FD_CLR(client->fd, &allwrites) ;
      sClientDestroy (currentClientp,*currentClientp) ;
    }
}


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

/* UUUGGGHHH, I don't really like this interface but it will have to do for  */
/* now....don't go altering it without looking at the cmd line arguments and */
/* select call.                                                              */
/*                                                                           */
/* routine returns:                                                          */
/*                    FALSE if client has not timed out or if special "no    */
/*                          time out" value set for clients (i.e. zero).     */
/*                                                                           */
/*                     TRUE if client has timed out                          */
/*                                                                           */
/* shortest_timeout is up updated each time if the time left for this client */
/* is shorter than the shortest so far, in particular, if a client has timed */
/* out then shortest_timeout is set to zero to ensure the client is killed   */
/* immediately.                                                              */
/*                                                                           */
static BOOL timeOut(S_CLIENT client, int clientTimeOut, int *shortest_timeout)
{
  BOOL result = TRUE ;

  if (clientTimeOut == ACESERV_INFINITE_TIMEOUT)
    result = FALSE ;
  else
    {
      int curr_time, elapsed_time, time_left ;

      curr_time = timeNow() ;
      elapsed_time = curr_time - client->time ;

      /* Check to see if client has timed out, if not then reset             */
      if (elapsed_time >= clientTimeOut)
	{
	  result = TRUE ;
	  *shortest_timeout = ACESERV_MIN_TIMEOUT ;	    /* Ensures client killed immediately. */
	}
      else
	{
	  time_left = clientTimeOut - elapsed_time ;
	  if (time_left < *shortest_timeout)
	    *shortest_timeout = time_left ;

	  result = FALSE ;
	}
    }

  return result ;
}

/* Reset clients timer to "now", this is only done when there is actually    */
/* about to be some communication between the server/client.                 */
static void resetTime(S_CLIENT client)
{

  client->time = timeNow() ;

  return ;
}



/**************************************************************/
/* disassociate for terminals, */

/* THIS DEFINITELY NEEDS SOME CHECKING...                                    */
static void setCleanEnv(void)
{
  /* in dec alpha documentation i found the following recommendations */
  setsid () ;   /* reset neutral environment */
  chdir ("/") ;
  umask (0) ;
  
  return ;
}


/************************************************************/
/* OK, create a socket that uses streams and bind to it.      
 * return -1: failed
 *         0: inetd case, inetd duplicates socket on stdin
 *       n>0: foreground socket
 *
 * NOTE, for inetd we ignore 'port', the port is set by in /etc/inetd.conf
 */
static int aceSocketCreate (int port)
{
  int  aceSocket = -1 ;
  int listen_port ;


  /* NOTE that ports and sockets are not the same numbers, the socket is the */
  /* file descriptor number for inetd this is 0, for non-inetd it could be   */
  /* any value.                                                              */
  if (isInetdDaemon(&listen_port))			    /* inetd case */
    {

      /* Really the fact that this is 0 should be encapsulated somewhere,    */
      /* the inetd routine knows this but its not the correct place.         */
      aceSocket = 0 ;


      setCleanEnv () ;

      if (debug_G)
	messout("Server listening socket inherited from inetd, port %d created", listen_port) ;
    }
  else
    { 
      int setSockOpt = 1 ;
      struct sockaddr_in sname ;
      
      aceSocket = socket (AF_INET, SOCK_STREAM, 0) ;
      if (aceSocket < 0)
	messcrash ("Cannot open server listening socket") ;

      /* Set SO_REUSEADDR option otherwise we get an "address already in use" error  */
      /* on the bind because there is some connection from a previous server incarnation */
      /* that has not died yet. (inetd also does this.)                      */
      if (setsockopt(aceSocket, SOL_SOCKET, SO_REUSEADDR,
		     &setSockOpt, sizeof(setSockOpt)) < 0)
	messcrash("Cannot set SO_REUSEADDR option for listening socket: %s", messSysErrorText()) ;

      /* create name with wildchar */
      bzero((char *)&sname, sizeof(sname));
      sname.sin_family = AF_INET ;
      sname.sin_addr.s_addr = htonl(INADDR_ANY) ;
      sname.sin_port = htons(port) ;
      
      if (bind(aceSocket, (struct sockaddr *) &sname, sizeof sname) < 0)
	messcrash ("Cannot bind to server listening socket %s", messSysErrorText()) ;

      if (listen(aceSocket, LISTENQ) < 0)
	messcrash ("Cannot listen to server listening socket %s",messSysErrorText()) ;

      if (debug_G)
	messout("Server listening socket %d created", aceSocket) ;
    }

  return aceSocket ;
}


/**************************************************************/
/* Our version of accept where we catch the additional EWOULDBLOCK error     */
/* because we have set our listening socket non-blocking.                    */
/*                                                                           */
/* When Posix1.g appears then this function will need to be changed because  */
/* ECONNABORTED will mean the client has abborted and EPROTO will mean there */
/* is a probably unrecoverable stream error so we should terminate the server*/
/* But for now this is a real mish/mash on different systems so we will just */
/* do our best.                                                              */
static int clientAccept(int fd, struct sockaddr *sa, socklen_t *salenptr)
{
  int socket = -1 ;

  if ( (socket = accept(fd, sa, salenptr)) < 0)
    {
      if (errno != EINTR && errno != EWOULDBLOCK
#ifdef	EPROTO
	  && errno != EPROTO && errno != ECONNABORTED
#else
	  && errno != ECONNABORTED
#endif
	  )
	messcrash("Unrecoverable error in accept() for new client, %s", messSysErrorText());
    }

  return socket ;

}


/* Check validity of return request from acedb code. Add any new checks to   */
/* here rather then cluttering up main socket code.                          */
static void checkServerReturn(AceSocketRequestTarget target, AceSocketRequestType request)
{

  if (target != ACESOCK_SERVER && target != ACESOCK_CLIENT)
    messcrash("Internal logic error, socket code received bad target from acedb server code:\n"
	      "currTarget currTarget has invalid value: %d \n"
	      "(should be %d or %d).",
	      target, ACESOCK_SERVER, ACESOCK_CLIENT) ; 

  if (target == ACESOCK_SERVER
      && (request != ACESOCK_SHUTFORCE && request != ACESOCK_SHUTQUIESCE))
    messcrash("Internal logic error, socket code received bad request from acedb server code:\n"
	      "currTarget == ACESOCK_SERVER, but currRequest has invalid value: %d \n"
	      "(should be %d or %d).",
	      request, ACESOCK_SHUTFORCE, ACESOCK_SHUTQUIESCE) ; 

  if (target == ACESOCK_CLIENT
      && (request != ACESOCK_REQ && request != ACESOCK_KILL))
    messcrash("Internal logic error, socket code received bad request from acedb server code:\n"
	      "currTarget == ACESOCK_CLIENT, but currRequest has invalid value: %d \n"
	      "(should be %d or %d).",
	      request, ACESOCK_REQ, ACESOCK_KILL) ; 


  return ;
}



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