/*
 * sr_structcodec.c,v 1.2 1994/01/30 11:42:27 franktor Exp
 *
 * sr_structcodec.c
 *
 * Geir Pedersen, 1993
 *
 * Sr specific struct codec routiones. Builds on structcodec.c.
 * Interface towards the using code:
 *
        status = createListener ( connectionParameters, int *fd )
	status = connectToDatabaseServer ( connectionParameters, int *fd ) 
	status = closeConnection ( int fd )

*/

/*

Notes on the structure of a server

A server will handle calls from a client representing one or more SR
clients. One such connection will be used to handle requests from one
or more SR clients. createListener() will never return in the process
that calls it if the communication method specified is tcp/ip. It will
instead fork a subprocess to handle the actual connections. If the
communication method is a pipe createListener() will return in the
process that calls it, and it is expected that the process will die
when the pipe is broken.

*/                                                     


#ifndef VMS

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <arpa/inet.h>		/* Declare inet_addr (need /bin/cc???? -hbf) */
#include <signal.h>
#include <netdb.h>
#include <errno.h>
#include <ctype.h>
#include <sr-general.h>
#include <sr-api.h> 
#include <high/eapi.h>
#include <high/sr_structcodec.h>

#else /* VMS */

#define fork()			vfork() /* VAX C has no fork() */
#define bcopy(a,b,c)		memcpy(b,a,c)
#define bzero(b, length)	memset(b, 0, length) /* Should add (void) */

#include <sr-api.h>
/* #include <unixio.h> */
#include <sys/file.h>
#include <netinet/in.h>
#include <signal.h>
#include <sys/netdb.h>
/* errno.h has some useful(?) defines, but doesn't declare errno etc. */
#include <errno.h>
#include <sys/socket.h>
#include <ctype.h>
#include <eapi.h>
#include <sr_structcodec.h>
#include <sys/ioctl.h>

int connections;

#endif /* VMS */

typedef enum peerKind { peerInitiator, peerResponder } peerKind;

typedef struct pidlist {
  int pid;
  struct pidlist *next;
} pidlist;


pidlist *first_pid = NULL;

#if 0 /* Have to sort out wait before implementing this. -Frank */
void kill_children();
void check_deaths();
void add_pid(int i);
#endif
void wait_for_children();

/*
 * no_fork and set_no_fork() is mostly provided to ease debugging, not
 * for actual usage.
 */
int no_fork = 0;

void set_no_fork(int i)
{
  no_fork = i;
}


/* CreateListener */

srPktStatus CreateListener ( connectionParameters *coninfo, int *Fd,
                             elemDesc *struct_top )
{
   int 				sock; /* generic socket used for listening (in the case of tcp/ip) */
   int				errors = 0;	/* Avoid endless loops */
   int				i;

   if ( !coninfo )
      return srPktStat_failed;

#ifndef VMS
   signal(SIGCHLD, wait_for_children);
#endif
#if 0
   signal(SIGHUP, kill_children);
   signal(SIGINT, kill_children);
#endif

   switch ( coninfo->method )
   {
      struct sockaddr_in 	server;
      struct hostent 		*hp, *gethostbyname();
      int			clientSock; /* socket for a specific client */
      struct sockaddr_in	clientAddr;
      int			clientAddrLen;
      int                       tries;

    case connect_tcpip:
      /* Create socket */
      sock = socket ( AF_INET, SOCK_STREAM, 0 );
      if ( sock < 0 ) 
      {
	 LOG ( facStruct, llevExceptions, "Failed to get socket(): %s", strerror());
	 return srPktStat_failed;
      }
      {
#if 0
         /* Dette var verd et fors|k siden noen OSer vil ha             *
          * slike parametre, men det virket heller ikke.        --hbf   */
         int                    onoff = 1, *flag = &onoff, sz = sizeof(int);
#else
         int                    *flag = 0, sz = 0;
#endif
         if (setsockopt ( sock, SOL_SOCKET, SO_REUSEADDR, (char *) flag, sz ) < 0)
         {
            LOG ( facStruct, llevDebug, "Setsockopt er en tosk");
         }
      }

      bzero ( (char *) &server, sizeof ( struct sockaddr_in ) );
      server.sin_family = AF_INET;
      if ( coninfo->u.ip.address )
      {
         if ( isdigit(*coninfo->u.ip.address) )
         {
            server.sin_addr.s_addr = inet_addr(coninfo->u.ip.address);
         }
         else
         {
	    if ( !(hp = gethostbyname ( coninfo->u.ip.address )) )
	    {
	       LOG ( facStruct, llevExceptions, "Failed to set up listener for database server over tcp/ip because host address could not be resolved: %s",
		     coninfo->u.ip.address );
	       return srPktStat_failed;
	    }
	    bcopy ( hp->h_addr, &(server.sin_addr.s_addr), hp->h_length );
         }
      }
      else
	 bzero ( &(server.sin_addr.s_addr), sizeof ( server.sin_addr ) );
      server.sin_port = htons ( coninfo->u.ip.port );

      if ( bind ( sock, &server, sizeof ( server ) ) < 0 ) 
      {
	 close(sock);
	 LOG ( facStruct, llevExceptions, "Failed to bind socket listener for database server: %s",
	       strerror());
	 return srPktStat_failed;
      }

      if ( listen ( sock, 5 ) < 0 )
      {
	 close(sock);
	 LOG ( facStruct, llevExceptions, "Failed to listen to socket listener for database server: %s",
	       strerror() );
	 return srPktStat_failed;
      }
      clientAddrLen = sizeof(struct sockaddr_in);
      /* wait for connections and then fork a process to handle it */
      while ( 1 )
      {
#ifdef VMS
      if (connections) {
        int arg = 1;
        ioctl(sock, FIONBIO, &arg);
      }
#endif
	 if ( (clientSock = accept ( sock, &clientAddr, &clientAddrLen )) == -1 )
	 {
	    if ( errno == EINTR )
	       continue;
	    LOG ( facStruct, llevExceptions, "CreateListener() failed to accept new client: %s",
		  strerror() );
            if (++errors > 10)
              break; /* Added this to avoid endless loops */
	 }
	 else
	 {
            int child_pid;
	    errors = 0;

	    if ( no_fork || (child_pid = fork()) == 0 )
	    {
	       /* CHILD */
	       close ( sock );
	       sock = clientSock;
	       break;		/* break out of while(1) to return to caller */
	    }
	    else
	    {
	       /* MOTHER */
               LOG ( facStruct, llevDebug, "CreateListener() made child: %d.",
                     child_pid);
#if 0
               add_pid(child_pid);
#endif
	       close ( clientSock ); /* get ready to accept next client */
	    }
	 }
#if 0
         check_deaths();
#endif
      }
#ifdef VMS
      break;
#else
    *Fd = sock;

      initialise_socket ( sock, struct_top, struct_top );

      LOG ( facStruct, llevNotice, "CreateListener() accepted a new client (%d): %s+%d",
	    sock, inet_ntoa(clientAddr.sin_addr), clientAddr.sin_port );
      return srPktStat_ok;
#endif /* VMS */

#ifndef VMS
    case connect_tcpip_multi:
      /* Create socket */
      sock = socket ( AF_INET, SOCK_STREAM, 0 );
      if ( sock < 0 ) 
      {
	 LOG ( facStruct, llevExceptions, "Failed to get socket(): %s", strerror());
	 return srPktStat_failed;
      }

      /* set reuse of address */
      (void) setsockopt ( sock, SOL_SOCKET, SO_REUSEADDR, (char *) 0, 0 ); 

      /* make the socket non-blocking */
      i = 1;
      if ( ioctl ( sock, FIONREAD, (char *) &i ) != 0 )
      {
	 LOG ( facStruct, llevExceptions, "Failed to make socket non-blocking: %s",
	       strerror() );

	 close ( sock );
	 return srPktStat_failed;
      }

      bzero ( (char *) &server, sizeof ( struct sockaddr_in ) );
      server.sin_family = AF_INET;
      if ( coninfo->u.ip.address )
      {
         if ( isdigit(*coninfo->u.ip.address) )
         {
            server.sin_addr.s_addr = inet_addr(coninfo->u.ip.address);
         }
         else
         {
	    if ( !(hp = gethostbyname ( coninfo->u.ip.address )) )
	    {
	       LOG ( facStruct, llevExceptions, "Failed to set up listener for database server over tcp/ip because host address could not be resolved: %s",
		     coninfo->u.ip.address );
	       return srPktStat_failed;
	    }
	    bcopy ( hp->h_addr, &(server.sin_addr.s_addr), hp->h_length );
         }
      }
      else
	 bzero ( &(server.sin_addr.s_addr), sizeof ( server.sin_addr ) );
      server.sin_port = htons ( coninfo->u.ip.port );

      if ( bind ( sock, &server, sizeof ( server ) ) < 0 ) 
      {
	 close(sock);
	 LOG ( facStruct, llevExceptions, "Failed to set up listener for database server: %s",
	       strerror());
	 return srPktStat_failed;
      }

      if ( listen ( sock, 5 ) < 0 )
      {
	 close(sock);
	 LOG ( facStruct, llevExceptions, "Failed to set up listener for database server: %s",
	       strerror() );
	 return srPktStat_failed;
      }
      clientAddrLen = sizeof(struct sockaddr_in);

      *Fd = sock;

      LOG ( facStruct, llevDebug, "CreateListener() created socket fd=%d to be used by a multi server", sock );
      return srPktStat_ok;
      break;

#else /* VMS */
    case connect_tcpip_monolith:
      sock = coninfo->u.ip.socket;

      /* Create socket if not done earlier */
      if (sock == -1) {

	LOG ( facStruct, llevDebug, "Opening socket");
	tries=0;
	while((sock < 0) /* && (tries < MAX_CON_TRIES) Removed bug check */){
	  tries++;
	  sock = socket ( AF_INET, SOCK_STREAM, 0 );
	  LOG ( facStruct, llevDebug, "socket() returned %d", sock );
	  if (sock < 0) {
	    LOG(facStruct, llevExceptions,
		"Failed to get socket(): %s. (Try %d)", strerror(), tries);
	    sleep(3);
	    continue;   /* Try again if not max_tries */
	  }
	  (void) setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, 0, 0);
	  coninfo->u.ip.socket = sock;

	  bzero((char *) &server, sizeof(struct sockaddr_in));
	  server.sin_family = AF_INET;
	  if (coninfo->u.ip.address) {
	    if (isdigit(*coninfo->u.ip.address))
	      server.sin_addr.s_addr = inet_addr(coninfo->u.ip.address);
	    else {
	      if (!(hp = gethostbyname(coninfo->u.ip.address))) {
		LOG(facStruct, llevExceptions, 
		    "Failed to set up listener for database server over tcp/ip because host address could not be resolved: %s (Try %d)",
		    coninfo->u.ip.address, tries);
		close(sock);
		sock = -1;
		sleep(3);
		continue;
	      }
	      bcopy ( hp->h_addr, &(server.sin_addr.s_addr), hp->h_length );
	    }
	  } else
	    bzero(&(server.sin_addr.s_addr), sizeof(server.sin_addr));
	  server.sin_port = htons(coninfo->u.ip.port);
	  if (bind(sock, &server, sizeof(server)) < 0) {
	    close(sock);
	    LOG(facStruct, llevExceptions,
		"Failed to bind up listener for database server: %s (sockno=%d) (Try %d)", strerror(errno), sock, tries);
	    sock = -1;
	    sleep(3);
	    continue;
	  }

	  if (listen(sock, 5) < 0) {
	    close(sock);
	    LOG(facStruct, llevExceptions,
		"Failed to set up listener for database server: %s (Try %d", 
		strerror(), tries );
	    sock = -1;
	    sleep(3);
	    continue;
	  }
	}
      }
      if(sock < 0) return srPktStat_failed;

      clientAddrLen = sizeof(struct sockaddr_in);
      /* wait for connections or just poll if we already have one */
      do {
	/* Do non-blocking accept() when there are connections */
	int arg = connections ? 1 : 0;
	ioctl(sock, FIONBIO, &arg);
	  
	if ((clientSock = accept(sock, &clientAddr, &clientAddrLen)) == -1) {
	  if (errno == EINTR)
	    continue;
	  if (errno == EWOULDBLOCK) {
	    if (connections)
	      return srPktStat_failed;
	    else
	      continue;
	  }
	  LOG(facStruct, llevExceptions,
	      "CreateListener() failed to accept new client: %s", strerror() );
	  if (++errors > 10)
	    break; /* Added this to avoid endless loops */
	} else {
	  errors = 0;
	  sock = clientSock;
	  ++connections;
	  break;		/* break out of while(1) to return to caller */
	}
      } while (connections == 0);
      break;
#endif /* VMS */

    case connect_pipe:
      LOG ( facStruct, llevExceptions, "CreateListener() connect_pipe not yet implemented" );
      return srPktStat_failed;
      
    case connect_none:
      LOG ( facStruct, llevExceptions, "CreateListener() no connection method specified" );
      return srPktStat_failed;
      
    default:
      LOG ( facStruct, llevExceptions, "CreateListener() unknown connection method specified" );
      return srPktStat_failed;
   }

#ifdef VMS
   *Fd = sock;

   initialise_socket ( sock, struct_top, struct_top );

   LOG ( facStruct, llevDebug, "CreateListener() created socket fd=%d", sock );

   return srPktStat_ok;
#endif
   /* NOTREACHED */
}



#ifndef VMS

Boolean CreateListener2 ( int fd, int *Fd, elemDesc *struct_top )
{
   int			clientSock;
   struct sockaddr_in	clientAddr;
   int			clientAddrLen = sizeof(struct sockaddr);
   
   /* the socket is non-blocking - check if there is something to accept */
 again:
   if ( (clientSock = accept ( fd, &clientAddr, &clientAddrLen )) == -1 )
   {
      if ( errno == EINTR )
	 goto again;
      LOG ( facStruct, llevExceptions, "CreateListener2() failed to accept new client: %s",
	    strerror() );

      return False;
   }

   initialise_socket ( clientSock, struct_top, struct_top );

   LOG ( facStruct, llevNotice, "CreateListener2() accepted a new client (%d): %s+%d",
	 clientSock, inet_ntoa(clientAddr.sin_addr), clientAddr.sin_port );

   *Fd = clientSock;

   return True;
}

#endif /* VMS */



/* ConnectToDatabaseServer */

srPktStatus ConnectToDatabaseServer ( connectionParameters *coninfo, int *Fd,
                                      elemDesc *struct_top )
{
   int 				sock;

   if ( !coninfo )
      return srPktStat_failed;

   switch ( coninfo->method )
   {
      struct sockaddr_in 	server;
      struct hostent 		*hp, *gethostbyname();

    case connect_tcpip:
      /* Create socket */
      sock = socket ( AF_INET, SOCK_STREAM, 0 );
      if ( sock < 0 ) 
      {
	 LOG ( facStruct, llevExceptions, "Failed to get socket(): %s", strerror() );
	 return srPktStat_failed;
      }

      server.sin_family = AF_INET;
      server.sin_port = htons ( coninfo->u.ip.port );
      if (isdigit(*coninfo->u.ip.address))
      {
         server.sin_addr.s_addr = inet_addr(coninfo->u.ip.address);
      }
      else
      {
         if ( !(hp = gethostbyname ( coninfo->u.ip.address )) )
         {
	    LOG ( facStruct, llevExceptions, "Failed to connect to database server over tcp/ip because host address could not be resolved: %s", coninfo->u.ip.address );
	    return srPktStat_failed;
         }
         bcopy ( hp->h_addr, &(server.sin_addr.s_addr), hp->h_length );
      }

      LOG ( facStruct, llevDebug, "Attempting connection to %s, port %d.",
           coninfo->u.ip.address, coninfo->u.ip.port);

      if ( connect ( sock, &server, sizeof ( server ) ) < 0 ) 
      {
	 close(sock);
	 LOG ( facStruct, llevExceptions, "Failed to connect to database server: %s", strerror() );
	 return srPktStat_failed;
      }
      break;

    case connect_pipe:
      LOG ( facStruct, llevExceptions, "ConnectToDatabaseServer() connect_pipe not yet implemented" );
      return srPktStat_failed;
      
    case connect_none:
      LOG ( facStruct, llevExceptions, "ConnectToDatabaseServer() no connection method specified" );
      return srPktStat_failed;

#ifndef VMS
    case connect_tcpip_multi:
      LOG ( facStruct, llevExceptions, "ConnectToDatabaseServer() connection method connect_tcpip_multi is not leagal for this function" );
      return srPktStat_failed;
#endif

    default:
      LOG ( facStruct, llevExceptions, "ConnectToDatabaseServer() unknown connection method specified" );
      return srPktStat_failed;
   }

   *Fd = sock;

   initialise_socket ( sock, struct_top, struct_top );

   LOG ( facStruct, llevDebug, "ConnectToDatabaseServer() returning successfully with fd=%d", sock );

   return srPktStat_ok;
}




/* closeConnection */

srPktStatus closeConnection ( int fd )
{
   LOG ( facStruct, llevDebug, "closeConnection() called for fd=%d", fd );

   close ( fd );
   free_socket ( fd );		/* Fjern sock fra structcodec.  -hbf */

   return srPktStat_ok;
}

/*
 * Routines to handle the linked list of child pids.
 */

#if 0 /* Not used yet, haven't sorted out the wait-call yet. */
void add_pid(int i)
{
  pidlist *pid = (pidlist *) malloc(sizeof(pidlist));
  pid->pid = i;
  pid->next = first_pid;
  first_pid = pid;
}

void remove_pid(int i)
{
  pidlist **pp, *pid;

  for(pp = &first_pid; (pid = *pp) != NULL; pp = &pid->next)
    if(pid->pid == i)
      break;
  if (!pid)
  {
    LOG(facStruct, llevExceptions, "remove_pid: No such pid (%d).", i);
    return;
  }
  *pp = pid->next;
}
#endif

#if 0
void kill_children()
{
  pidlist *pid;
  for (pid = first_pid; pid; pid = pid->next)
    if (kill(pid->pid, SIGHUP) == -1)
      LOG(facStruct, llevExceptions, "kill_children: %s", strerror());
  exit(0);
}

void check_deaths()
{
  if (first_pid == (pidlist *) NULL)
    return;
  /* wait3(,WWNOHANG,); */
}
#endif

void wait_for_children() {
#ifndef VMS
  int statusp;
  struct rusage rusage;
  int pid;
  
  pid = wait3(&statusp,WNOHANG,&rusage);
  LOG(facStruct, llevDebug, "Caught child %d: 0x%x\n", pid, statusp);
#endif
}
