/***************************************
  $Revision: 1.6 $

  Example code: A socket module.

  Status: NOT REVUED, NOT TESTED

  +html+ <DL COMPACT>
  +html+ <DT>Online References:
  +html+ <DD><UL>
  +html+   <LI>Adapted from <A HREF="http://www.ibrado.com/sock-faq/sfaq.html#faq65">sample source code</A>.
  +html+ </UL>
  +html+ </DL>
  +html+ <PRE>
  +html+ </PRE>
 
  ******************/ /******************
  Modification History:
        ottrey (08/03/1999) Created from sockhelp.c.
        ottrey (08/03/1998) Heavily butchered.
        joao   (22/06/1999) Modified socket creation and accepts.
  ******************/ /******************
 REMINDER: PUT THE PROPER COPYRIGHT NOTICE HERE
  ***************************************/
#include <arpa/inet.h>
#include "socket.h"
#include "constants.h"
#include "stubs.h"

#include "iproutines.h"
#include "memwrap.h"

#include <pthread.h>

extern int h_errno;


/*+ String sizes +*/
#define STR_S   63
#define STR_M   255
#define STR_L   1023
#define STR_XL  4095
#define STR_XXL 16383

/* SK_atoport() */
/*++++++++++++++++++++++++++++++++++++++
   Take a service name, and a service type, and return a port number.  If the
   service name is not found, it tries it as a decimal number.  The number
   returned is byte ordered for the network.

  char *service   Service name (or port number).

  char *proto     Protocol (eg "tcp").

  More:
  +html+ <PRE>
  Authors:
        ottrey

  +html+ </PRE><DL COMPACT>
  +html+ <DT>Online References:
  +html+ <DD><UL>
  +html+ </UL></DL>

  ++++++++++++++++++++++++++++++++++++++*/
int SK_atoport(const char *service, const char *proto) {
  int port;
  long int lport;
  struct servent *serv;
  char *errpos;
  struct servent result;
  char buffer[STR_XXL];

  /* First try to read it from /etc/services */

  /*  serv = getservbyname(service, proto); */
  serv = getservbyname_r(service, proto, &result, buffer, sizeof(buffer));
  if (serv != NULL)
    port = serv->s_port;
  else { /* Not in services, maybe a number? */
    lport = strtol(service,&errpos,0);
    if ( (errpos[0] != 0) || (lport < 1) || (lport > 65535) )
      return -1; /* Invalid port address */
    port = htons(lport);
  }
  return port;
} /* SK_atoport() */


/* SK_close() */
/*++++++++++++++++++++++++++++++++++++++
  
  More:
  +html+ <PRE>
  Authors:
        ottrey

  +html+ </PRE><DL COMPACT>
  +html+ <DT>Online References:
  +html+ <DD><UL>
  +html+ </UL></DL>

  ++++++++++++++++++++++++++++++++++++++*/
int SK_close(int socket) {
  ER_dbg_va(FAC_SK, ASP_SK_GEN, "Closing socket... %d", socket);

  return close(socket);
}

/* SK_getsock() */
/*++++++++++++++++++++++++++++++++++++++

   This function creates a socket and binds to it

   int      SK_getsock       The new socket

   int      socket_type      SOCK_STREAM or SOCK_DGRAM (TCP or UDP sockets)

   u_short  port             The port to listen on.  Remember that ports < 1024 are
                             reserved for the root user.  Must be passed in network byte
                             order (see "man htons").

   uint32_t bind_address     Address to bind to, in network order.
  More:
  +html+ <PRE>
  Authors:
        ottrey
	joao

  +html+ </PRE><DL COMPACT>
  +html+ <DT>Online References:
  +html+ <DD><UL>
  +html+ </UL></DL>

  ++++++++++++++++++++++++++++++++++++++*/
int SK_getsock(int socket_type, u_short port, uint32_t bind_address) {
  struct sockaddr_in address;
  int listening_socket;
  int reuse_addr = 1;

  /* Setup internet address information.  
     This is used with the bind() call */
  memset((char *) &address, 0, sizeof(address));
  address.sin_family = AF_INET;
  address.sin_port = port;
  address.sin_addr.s_addr = bind_address;

  /* Map all of the signals and exit routine */

  listening_socket = socket(AF_INET, socket_type, 0);
  if (listening_socket < 0) {
    perror("socket");
    exit(EXIT_FAILURE);
  }

  setsockopt(listening_socket, SOL_SOCKET, SO_REUSEADDR, (void *)&reuse_addr, sizeof(reuse_addr));

  if (bind(listening_socket, (struct sockaddr *) &address, sizeof(address)) < 0) {
    perror("bind");
    close(listening_socket);
    exit(EXIT_FAILURE);
  }


  if (socket_type == SOCK_STREAM) {
    listen(listening_socket, 5); /* Queue up to five connections before
                                  having them automatically rejected. */
  }

  return listening_socket;
} /* SK_getsock() */

/*++++++++++++++++++++++++++++++++++++++

   Wait for an incoming connection on the specified socket

   int	SK_accept_connection The socket for communicating to the client

   int  listening_socket     The socket that the server is bound to

  More:
  +html+ <PRE>
  Authors:
	joao
  +html+ </PRE>
  ++++++++++++++++++++++++++++++++++++++*/
int SK_accept_connection(int listening_socket) {
  int connected_socket = -1;

  while(connected_socket < 0) {
    
    ER_dbg_va(FAC_SK, ASP_SK_GEN, 
	      "Going to accept connections on socket : %d",listening_socket);

/* XXX joao - ? - why is this here?
fflush(NULL);
*/

    connected_socket = accept(listening_socket, NULL, NULL);
    if (connected_socket < 0) {
      /* Either a real error occured, or blocking was interrupted for
         some reason.  Only abort execution if a real error occured. */
      if (errno != EINTR) {
        perror("accept");
        close(listening_socket);
        return(-1);
     /* no exit, just return with error */
      } else {
        continue;    /* don't return - do the accept again */
      }
    }
  }

  ER_dbg_va(FAC_SK, ASP_SK_GEN, "client connected on socket %d", 
	    connected_socket
	    );

  return connected_socket;
}

/* SK_read() */
/*++++++++++++++++++++++++++++++++++++++

   This is just like the read() system call, except that it will make
   sure that all your data goes through the socket.

   int    SK_read  The number of bytes read.

   int    sockfd    The socket file descriptor.

   char   *buf      The buffer to be read from the socket.

   size_t count     The number of bytes in the buffer.

  More:
  +html+ <PRE>
  Authors:
        ottrey
  +html+ </PRE>
  ++++++++++++++++++++++++++++++++++++++*/
int SK_read(int sockfd, char *buf, size_t count) {
  size_t bytes_read = 0;
  int this_read;

  while (bytes_read < count) {
    do
      this_read = read(sockfd, buf, count - bytes_read);
    while ( (this_read < 0) && (errno == EINTR) );
    if (this_read < 0)
      return this_read;
    else if (this_read == 0)
      return bytes_read;
    bytes_read += this_read;
    buf += this_read;
  }

  return count;

} /* SK_read() */


/* SK_write() */
/*++++++++++++++++++++++++++++++++++++++

   This is just like the write() system call, accept that it will
   make sure that all data is transmitted.

   int    sockfd  The socket file descriptor.

   char   *buf    The buffer to be written to the socket.

   size_t count   The number of bytes in the buffer.

  More:
  +html+ <PRE>
  Authors:
        ottrey

  +html+ </PRE><DL COMPACT>
  +html+ <DT>Online References:
  +html+ <DD><UL>
  +html+ </UL></DL>

  ++++++++++++++++++++++++++++++++++++++*/
int SK_write(int sockfd, const char *buf, size_t count) {
  size_t  bytes_sent = 0;
  int     this_write;

  
  ER_dbg_va(FAC_SK, ASP_SK_WRIT,
	    "SK_write = { sockfd=[%d], buf=[%s], count=[%d]", 
	    sockfd, buf, count);

  while (bytes_sent < count) {
    do
      this_write = write(sockfd, buf, count - bytes_sent);
    while ( (this_write < 0) && (errno == EINTR) );
    if (this_write <= 0)
      return this_write;
    bytes_sent += this_write;
    buf += this_write;
  }
  return count;
} /* SK_write() */


/* SK_gets() */
/*++++++++++++++++++++++++++++++++++++++

   This function reads from a socket, until it recieves a linefeed
   character.  It fills the buffer "str" up to the maximum size "count".

   int SK_gets  The total_count of bytes read.

   int    sockfd    The socket file descriptor.

   char   *str      The buffer to be written from the socket.

   size_t count     The number of bytes in the buffer.

  More:
  +html+ <PRE>
  Authors:
        ottrey

  Side Effects:
        This function will return -1 if the socket is closed during the read operation.

        Note that if a single line exceeds the length of count, the extra data
        will be read and discarded!  You have been warned.

  To Do:
        Capture the control-c properly!

  +html+ </PRE>

  ++++++++++++++++++++++++++++++++++++++*/
int SK_gets(int sockfd, char *str, size_t count) {
  int bytes_read;
  int total_count = 0;
  char *current_position;
  char last_read = 0;

  int control_c = 0;

  current_position = str;
  while (last_read != 10) {

    

    bytes_read = read(sockfd, &last_read, 1);
    if (bytes_read <= 0) {
      /* The other side may have closed unexpectedly */
      return SK_DISCONNECT; 
      /* Is this effective on other platforms than linux? */
    }
    if ( (total_count < count) && (last_read != 10) && (last_read !=13) ) {
      *current_position = last_read;
      current_position++;
      total_count++;
    }

    if (last_read == -1) {
      bytes_read = read(sockfd, &last_read, 1);
      if (last_read == -12) {
        ER_dbg_va(FAC_SK, ASP_SK_GEN,"Client pressed Control-c");
        control_c = 1;
        ER_dbg_va(FAC_SK, ASP_SK_GEN,"returning SK_INTERRUPT");
        return SK_INTERRUPT;
      }
    }
  }
  if (count > 0) {
    *current_position = 0;
  }

  return total_count;

} /* SK_gets() */


/* SK_puts() */
/*++++++++++++++++++++++++++++++++++++++

   This function writes a character string out to a socket.

   int SK_puts  The total_count of bytes written, 
                or errors (represented as negative numbers)

   int    sockfd    The socket file descriptor.

   char   *str      The buffer to be written from the socket.

  More:
  +html+ <PRE>
  Authors:
        ottrey

  Side Effects:
        This function will return -1 if the socket is closed during the write operation.

        Note that if a single line exceeds the length of count, the extra data
        will be read and discarded!  You have been warned.

  +html+ </PRE>

  ++++++++++++++++++++++++++++++++++++++*/
int SK_puts(int sockfd, const char *str) {

  return SK_write(sockfd, str, strlen(str));

} /* SK_puts() */

/* SK_putc() */
/*++++++++++++++++++++++++++++++++++++++

   int SK_putc This function writes a single character out to a socket.

   int sockfd        socket
   char ch           character

   return number of chars written 

  ++++++++++++++++++++++++++++++++++++++*/
int SK_putc(int sockfd, char ch) {
  return SK_write(sockfd, &ch, 1);
}/* SK_putc() */

/*++++++++++++++++++++++++++++++++++++++

   This function reads a single character from a socket.

   returns EOF when no character can be read. 

  ++++++++++++++++++++++++++++++++++++++*/
int SK_getc(int sockfd) {
  char ch;

  if( read(sockfd, &ch, 1) <= 0 ) {
    return EOF;
  }
  else {
    return ch;
  }
}/* SK_getc() */

/* SK_getpeername() */
/*++++++++++++++++++++++++++++++++++++++

   This function will tell you who is at the other end of a connected stream socket.
   XXX It's not working.
   XXX ? MB it is...

   int    sockfd    The socket file descriptor.

  More:
  +html+ <PRE>
  Authors:
        ottrey
  +html+ </PRE>

  ++++++++++++++++++++++++++++++++++++++*/
char *SK_getpeername(int sockfd) 
{
  char *hostaddress=NULL;
  struct sockaddr_in addr_in;
  int namelen=sizeof(addr_in);
 
  if (getpeername(sockfd, (struct sockaddr *)&addr_in, &namelen) != -1) {

    dieif( wr_malloc((void **)&hostaddress, 16) != UT_OK); 
    
    strcpy(hostaddress, inet_ntoa(addr_in.sin_addr));  /* XXX MT-UNSAFE */
  }

  return hostaddress;
  
} /* SK_getpeername() */

/* SK_getpeerip */
int SK_getpeerip(int sockfd, ip_addr_t *ip) {
  struct sockaddr_in addr_in;
  int namelen=sizeof(addr_in);
  int ret=-1;

  memset(& addr_in, 0, sizeof(struct sockaddr_in));

  if (getpeername(sockfd, (struct sockaddr *)(& addr_in), &namelen) != -1) {
    ret=0;
    IP_addr_s2b(ip, &addr_in, namelen);
  }
  
  return ret;
}

/*-------------------------------------------------------------------
 *   CD varieties of the functions: broken connections get registered
 *   in the connection structure within the query environment 
 *   as side effects.
 * -----------------------------------------------------------------*/

/* SK_cd_puts() */
/*++++++++++++++++++++++++++++++++++++++

   This function writes a character string out to a socket.

   int SK_qe_puts  The total_count of bytes written, 
                or errors (represented as negative numbers)

   sk_conn_st *condat connection data

   char   *str       The buffer to be written from the socket.

  More:
       if the connection structure has bad status for this connection
       from previous calls, no write will be attempted.

  +html+ <PRE>
  Authors:
        marek

  Side Effects:
       broken connections get registered
       in the connection structure within the query environment 
	
  +html+ </PRE>

  ++++++++++++++++++++++++++++++++++++++*/
int SK_cd_puts(sk_conn_st *condat, const char *str) {
  int res=SK_puts(condat->sock, str);

  if( res < 0 ){
    /* set the corresponding rtc flag */
    condat->rtc |= (-res);

    switch( - res ) {
      /* dont know what to do and how to log */
    case SK_DISCONNECT:
    case SK_INTERRUPT:
      /*("Thread received a control-c\n");*/
    case SK_TIMEOUT:
      /*("Reading timed out\n");*/
      break;
    default:
      /* unexpected error code. bail out */
      die;
    }
  }
  return res;
} /* SK_cd_puts() */

/* SK_cd_gets() */
/*++++++++++++++++++++++++++++++++++++++

   Wrapper around SK_gets.

   int SK_cd_gets  The total_count of bytes read, 
                   or errors (represented as negative numbers)

   sk_conn_st *condat connection data

   char   *str       The buffer to be written from the socket.

  More:
       if the connection structure has bad status for this connection
       from previous calls, no write will be attempted.

  +html+ <PRE>
  Authors:
        marek
	
  Side Effects:
       broken connections get registered
       in the connection structure within the query environment 
       
  +html+ </PRE>

  ++++++++++++++++++++++++++++++++++++++*/
int SK_cd_gets(sk_conn_st *condat, char *str, size_t count) {
  fd_set rset;
  struct timeval *ptm = & condat->rd_timeout;
  int readcount = 0;
  
  memset( str, 0, count);
  FD_ZERO( &rset );
  FD_SET( condat->sock, &rset );

  if( ptm->tv_sec == 0 && ptm->tv_usec == 0) { /* if timeout undefined, 
						  do blocking I/O */
    ptm = NULL;
  }

  do {
    char buf[2];
    int sel = select( (condat->sock)+1, &rset, NULL, NULL, ptm);    

    dieif(sel < 0); /* we don't expect problems */
      
    if( sel == 0 ) {      
      condat->rtc |= SK_TIMEOUT;
      break;
    }

    else { 
      read( condat->sock, buf, 1 );
      str[readcount] = buf[0];
      readcount++;
      if( buf[0] == '\n' ) {
	break;
      }
    } 
  } while( readcount < count );
	 
  return readcount;

} /* SK_cd_gets() */


int SK_cd_close(sk_conn_st *condat) {
  return SK_close(condat->sock);
} /* SK_cd_close() */


/* print to condat like printf

   by marek
*/
int SK_cd_printf(sk_conn_st *condat, char *txt, ...)
{
#define SKBUFLEN 2047
  va_list   ap;
  char      buffer[SKBUFLEN+1];
  int       len;
  char      *newbuf = NULL;
  char      *finalbuf = buffer; /* points to where the text REALLY is */
 
  /* vsnprintf returns the number of character it WOULD write if it could.
     So we assume the buffer to be of adequate size for most cases,
     and if it isn't, then we allocate to newbuf and call v*printf again 
  */
  va_start(ap, txt);
  len = vsnprintf(buffer, SKBUFLEN, txt, ap);
  va_end(ap);
  
  if( len > SKBUFLEN ) {
    dieif(!NOERR(wr_malloc( (void **)& newbuf, len+1)));
    
    va_start(ap, txt);
    vsnprintf(newbuf, len, txt, ap);
    va_end(ap);   
    
    finalbuf = newbuf;
  }  
  /* terminate */
  finalbuf[len] = 0;

  /* reuse len */
  len = SK_cd_puts(condat, finalbuf);

  if(newbuf != NULL) {
    wr_free(newbuf);
  }

  return len;
}

/* =========================== watchdog =========================== */

#define ONCE_INIT 0xABCDEF
static pthread_key_t sk_watch_tsd = ONCE_INIT;

void SK_init(void)
{
  /* can be called only once */
  dieif( sk_watch_tsd != ONCE_INIT );
  dieif( pthread_key_create( &sk_watch_tsd, NULL) != 0 );
}

/* sk_watchdog signal handler */
static void func_sigusr(int n) {
  int *tsd_flag = (int *) pthread_getspecific(sk_watch_tsd);

  ER_dbg_va(FAC_SK, ASP_SK_GEN,"func_sigusr(%d) called", n);

  /* set a thread-specific flag that the handler was invoked */
  
  pthread_setspecific(sk_watch_tsd, (void *)1 );
}

/* sk_watchdog - started as a separate thread.

   selects on the given socket; discards all input.
   whenever it sees end of file (socket closed), it
   * sets a corresponding flag in the condat structure, 
   * kills a thread designated to be killed (by SK_watchkill)

   by marek;
*/
static
void *sk_watchdog(void *arg)
{
  sk_conn_st *condat = (sk_conn_st *) arg;
  int nready;
  int n;
  fd_set rset;
  char buff[STR_S];
  int socket = condat->sock;
  sigset_t sset;
  struct sigaction act;
  
  struct timeval timeout = { 1, 0 }; /* it's a timeout of 1 second */

  FD_ZERO(&rset);
  FD_SET(socket, &rset);

  sigemptyset(&sset);
  sigaddset(&sset, SIGUSR1);
  
  act.sa_handler = func_sigusr;
  act.sa_flags = 0;
  dieif(sigaction(SIGUSR1, &act, NULL) != 0);

  /* XXX in fact, it's unblocked already. Should be blocked on startup */
  dieif(pthread_sigmask(SIG_UNBLOCK, &sset, NULL) != 0);
  
  /* clear the handler's flag */
  pthread_setspecific(sk_watch_tsd, NULL);
  
  /* now ready for signal */
  pthread_mutex_unlock( & condat->watchmutex ); 

  /* hey, viva threaded signal handling! There is no way for select
     to unblock a blocked signal, It must be done by "hand" (above).

     Consequently, every once in a while, the signal will be delivered
     before the select starts :-/. So, we have to introduce a timeout
     for select and check if the signal was delivered anyway....aARGH!!!

     This adds a <timeout interval> to unlucky queries, about 0.1% of all.
  */

  while ((nready=select(socket+1, &rset, NULL, NULL, &timeout))!=-1) {
    
    ER_dbg_va(FAC_SK, ASP_SK_GEN,"select returned %d", nready);

    /* don't even try to read if we have been killed */
    if( errno == EINTR || pthread_getspecific(sk_watch_tsd) != NULL ) {
      break;
    }

    /* retry if the timeout has triggered */
    if( nready == 0 ) {
      continue;
    }

   /* There was some input or client half of connection was closed */
   /* Check for the latter */
   if (( n=read(socket, buff, sizeof(buff))) == 0) {
   /* Connection was closed by client */
   /* Now send a cancellation request to the whois thread. */
   /* mysql thread will be terminated by thread cleanup routine */
   
     /* set the reason-to-close flag on this connection */
     condat->rtc |= SK_INTERRUPT;

     /* cancel the thread to be cancelled if defined */
     if( condat->killthis != 0 ) {
       pthread_cancel(condat->killthis);
       /* The only possible error is ESRCH, so we do not care about it*/
     }

     /* call the function to be called if defined */
     if( condat->execthis != NULL ) {
       condat->execthis(condat->execargs);
     }

     /* quit */
     break;
   }
   /* Otherwise dump input and continue */

  }

  /* Exit the watchdog thread, passing NULL as we don't expect a join */
  pthread_exit(NULL);

  /* oh yes. Shouldn't compilers _analyze_ library functions ? */
  return NULL;
}
/* SK_watchstart

   starts sk_watchdog thread unless already started,
   and registers its threadid in the condat structure

   dies if watchdog already running
*/
er_ret_t
SK_watchstart(sk_conn_st *condat)
{
  dieif( condat->watchdog != 0 );
  
  /* init the mutex in locked state, watchdog will unlock it when 
     it's ready for signal */
  pthread_mutex_init( & condat->watchmutex, NULL );
  pthread_mutex_lock( & condat->watchmutex ); 

  pthread_create(&condat->watchdog, NULL, sk_watchdog, (void *) condat );
  
  return SK_OK;
}


/* SK_watchstop 

   stops sk_watchdog thread if it is registered in the connection struct
*/
er_ret_t
SK_watchstop(sk_conn_st *condat)
{
  void *res;

  if(condat->watchdog > 0) {
    int ret;

    /* wait until the watchdog is ready for signal */
    pthread_mutex_lock( & condat->watchmutex ); 

    ret = pthread_kill(condat->watchdog, SIGUSR1);
    
    ret = pthread_join(condat->watchdog, &res);
    
    pthread_mutex_destroy( & condat->watchmutex ); 
    condat->watchdog = 0;
  }
  return SK_OK;
}

/* SK_watchkill

   sets the threadid of the thread to be killed by watchdog
   0 means dont kill anything
*/
void
SK_watchkill(sk_conn_st *condat, pthread_t killthis)
{
  condat->killthis = killthis;
}

void
SK_watchexec( sk_conn_st *condat, void *(*function)(void *) , void *args)
{
  condat->execthis = function;
  condat->execargs = args;
}

void 
SK_watchclear(sk_conn_st *condat) 
{
  condat->execthis = NULL;
  condat->execargs = NULL;
  condat->killthis = 0;
}
