/* Server API for either a TCP connection oriented or a QNX
   message passing IPC methods. Both types are iterative.
   Rick Smereka, Copyright (C) 1998-2003.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, get a copy via the Internet at
   http://gnu.org/copyleft/gpl.html or write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston,
   MA 02111-1307 USA

   You can contact the author via email at rsmereka@future-lab.com

   Original version for CodeWarrior V4 under Windows 32bit.
   Dec/98, Rick Smereka

   Ported to HP-UX under GNU C 2.8.1.
   Jan/99, Rick Smereka

   Ported to Red Hat Linux 5.2, Jul/99, Rick Smereka

   Modified for Windows 32bit only. Removed the 'WSADATA' structure
   and all 'WSAStartup()' and WSACleanup()' function calls after I
   realized that the 'WSACleanup()' call would shutdown all WinSock
   operations. This causes trouble for socket servers which also
   perform client socket operations before replying to the cient.
   It is now the responsibility of the socket server to call
   'WSAStartup()' and 'WSACleanup()'. Also renamed 'clientSocket'
   to 'srvclientSocket' since CodeWarrior gets 'confused' with
   module globals with the same name. Dec/99, Rick Smereka

   Modified for use with both TCP sockets and QNX message passing
   IPC. Sep/2001, Rick Smereka

   Ported to Debian Linux. Nov/2002, Rick Smereka

   Changed call to 'bind' and 'accept' under Unix
   to use the 'SA' define (in 'flsocket.h').
   Apr/2003

   Complete re-write using TCP 'select' function (TCP IPC method only).
   The client socket is no longer closed once per transaction. Once the
   client connects, the socket is left open until a socket
   communication error or the client closes the socket. Model for this
   API taken from pages 162-167, section 6.8 of Unix Network Programming
   Volume 1, Networking API's: Sockets and XTI second edition by
   W. Richard Stevens. The 'select' TCP function is completly supported
   by WinSock, the only difference is that WinSock does not require
   or use the first parameter to 'select' which is the depth of
   the 'fd_set'. Along with the 'select' function, a set
   of socket descriptors is managed using the 'fd_set' data type
   and the 'FD_' macros which are completly portable within both Unix
   and Windows. Servers using this API are limited to the maximum
   number of client sockets in use defined by 'FD_SETSIZE' (in the
   OS socket include file).

   This API has been re-structured so that the function 'ipc_server_wait'
   is now an infinite loop and should only be called once by the
   main server code. When a request is sent by the client,
   'ipc_server_wait' will call the function 'process_request' to read
   the request data, process the request and send the reply back. Your
   server must supply this function which must return zero normally,
   one upon valid request of server termination and -1 upon some
   sort of socket communication error (refer to 'socloc.c', 'sys_log.c'
   and 'dbsrv.c' for good server examples of how to use this API).

   This API has also been modified to keep track of the current number
   of transactions processed and the current number of client
   connections (functions 'ipc_trans_num' and 'ipc_connect_num').  
   Note that all changes in this entry apply to TCP socket servers 
   only. May/2003, Rick Smereka

   Renamed function 'ipc_end' (QNX message passing only) to 'ipc_close'.
   Jun/2003, Rick Smereka */

#include "stdhead.h"
#include "flsocket.h"

#ifdef IPC_TCP
#include "ipcomm.h"
#endif

#include "ipcsrv.h"

// global (to module) socket data

#ifdef IPC_TCP
static int ipcsrv_maxfd;              // largest descriptor
static int ipcsrv_maxi;               // largest index into 'ipcsrv_client'
static fd_set ipcsrv_allset;          // set of 'fd's
static fd_set ipcsrv_rset;
static long ipcsrv_trans_no;          // transaction counter
#ifdef OS_WIN32
SOCKET serverSocket;                  // main server socket
SOCKET srvclientSocket;               // current client socket
SOCKET ipcsrv_client[FD_SETSIZE];
SOCKADDR_IN sockServerAddr;           // server address structure
#endif

#ifdef OS_UNIX
int ipcsrv_client[FD_SETSIZE];        // array of 'accept'ed fd's
int serverSocket, srvclientSocket;
struct sockaddr_in sockServerAddr;
#endif
#else

// QNX message passing module globals

int server_name_id;
pid_t server_pid;
#endif

#ifdef IPC_TCP
int ipc_init(char *host_name, int port_number)
#else
int ipc_init(char *sname)
#endif
{
   /* Initialize the server for communication. The port that the server 
      'listen's to is expected in 'port_number'
      for TCP servers. QNX message passing servers require
      the 'nameloc' service name (in 'sname'). Function returns 'TRUE'
      upon success,'FALSE' otherwise. Note that upon success,
      the local host name is returned in 'host_name' (TCP only)
      which must be already allocated to sufficient size. */

   char mname[] = "ipc_init";

#ifdef IPC_QNX
   /* initialize QNX server, start by requiring execution by
      the superuser */

   if (getuid() != 0)
      {
      log_file_date("%s:must be executed by superuser. Program abort", mname);
      return(FALSE);
      }

   if (sname == NULL || !strlen(sname))
      {
      log_file_date("%s:null or empty[sname]", mname);
      return(FALSE);
      }

   if ((server_name_id = qnx_name_attach(0, sname)) == -1)
      {
      log_file_date("%s:unable to attach name", mname);
      return(FALSE);
      }

   return(TRUE);
#else

   char *localHost;
   int i;

   if (port_number <= 0)
      {
      log_file_date("%s:invalid port. Program abort", mname);
      return(FALSE);
      }

   if (host_name == (char *)NULL)
      {
      log_file_date("%s:host name parm is NULL pointer", mname);
      return(FALSE);
      }

   host_name[0] = EOS;

   if ((localHost = (char *)malloc(1024)) == (char *)NULL)
      {
      log_file_date("%s:alloc fail 'localHost'. Program abort.", mname);
      return(FALSE);
      }

   // attempt to resolve the local host name

   if (gethostname(localHost, 1023))
      {
      free(localHost);
      log_file_date("%s:unable to resolve local host name. Program abort",
                    mname);
      return(FALSE);
      }

   log_file_date("%s:server on host %s, port is %d", mname, localHost, 
                 port_number);

#ifdef OS_WIN32
   serverSocket = socket(PF_INET, SOCK_STREAM, DEFAULT_PROTOCOL);
#endif

#ifdef OS_UNIX
   serverSocket = socket(AF_INET, SOCK_STREAM, DEFAULT_PROTOCOL);
#endif

   if (serverSocket == INVALID_SOCKET)
      {
      log_file_date("%s:unable to create server socket. Program abort",
                    mname);
      free(localHost);
      return(FALSE);
      }

   // populate server address structure

   memset(&sockServerAddr, 0, sizeof(sockServerAddr));
   sockServerAddr.sin_family = AF_INET;
   sockServerAddr.sin_addr.s_addr = INADDR_ANY;
   sockServerAddr.sin_port = htons(port_number);

   // bind the server socket

#ifdef OS_WIN32
   if (bind(serverSocket,(LPSOCKADDR)&sockServerAddr,sizeof(sockServerAddr))
       == SOCKET_ERROR)
#endif

#ifdef OS_UNIX
   if (bind(serverSocket, (SA *)&sockServerAddr, sizeof(sockServerAddr))
       == SOCKET_ERROR)
#endif

      {
      free(localHost);
      log_file_date("%s:unable to bind server socket.", mname);
      return(FALSE);
      }

   // listen on port

   if (listen(serverSocket, QUEUE_SIZE) == SOCKET_ERROR)
      {
      free(localHost);
      log_file_date("%s:unable to listen. Program abort", mname);
      return(FALSE);
      }

   /* set max fd and prepare the fd set for 'select' */

#ifdef OS_WIN32
   ipcsrv_maxfd = (int)serverSocket;
#else
   ipcsrv_maxfd = serverSocket;
#endif

   ipcsrv_maxi = -1;

   for(i = 0; i < FD_SETSIZE; i++)
      ipcsrv_client[i] = INVALID_SOCKET;

   FD_ZERO(&ipcsrv_allset);
   FD_SET(serverSocket, &ipcsrv_allset);
   strcpy(host_name, localHost);
   free(localHost);
   ipcsrv_trans_no = 0;
   return(TRUE);
#endif
}

#ifdef IPC_QNX
int ipc_close(void)
{
   /* Terminate server listening. QNX message passing
      only. Detaches name from 'nameloc'. Returns
      'TRUE' upon success, 'FALSE' otherwise. */

   if (qnx_name_detach(0, server_name_id) == -1)
      return(FALSE);

   return(TRUE);
}
#endif

#ifdef IPC_TCP
void ipc_server_wait(void)
{
   /* Accept client connections. Function
      returns only upon termination
      request or fatal error. Applies to TCP IPC only.
      This function calls a 'process_request'
      function to read the data, process the request
      and send a reply back. */

   char mname[] = "ipc_server_wait";
   int nready, len, i, ret;
#ifdef OS_WIN32
   SOCKET tempfd;
   SOCKADDR_IN cliaddr;
#else
   struct sockaddr_in cliaddr;
   int tempfd;
#endif

   /* main server socket 'select' and 'accept' loop (infinite except for
      sever termination */

   for(;;)
      {
      // create a copy of the main fd set and use it

      ipcsrv_rset = ipcsrv_allset;

      /* Windows 'select' does not use first parameter but we
         supply it anyway */

      if ((nready = select(ipcsrv_maxfd + 1, &ipcsrv_rset, NULL, NULL, NULL)) 
          <= 0)
         log_file_date("%s:bad rc[<= 0] from select", mname);

      // if there are new client connections, 'accept' them

      if (FD_ISSET(serverSocket, &ipcsrv_rset))
         {
         len = sizeof(cliaddr);

#ifdef OS_WIN32
         tempfd = accept(serverSocket, (LPSOCKADDR)&cliaddr, (LPINT)&len);
#endif

#ifdef OS_UNIX
         tempfd = accept(serverSocket, (SA *)&cliaddr, &len);
#endif

         if (tempfd == INVALID_SOCKET)
            log_file_date("%s:error accepting a connection", mname);

         /* place fd into an open element of the client array */

         for(i = 0; i < FD_SETSIZE; i++)
#ifdef OS_WIN32
            if ((int)ipcsrv_client[i] < 0)
#else
            if (ipcsrv_client[i] < 0)
#endif
               {
               ipcsrv_client[i] = tempfd;
               break;
               }

         // too many clients is fatal

         if (i == FD_SETSIZE)
            {
            log_file_date("%s:detected too many active clients", mname);
            return;
            }

         // add new fd to set

         FD_SET(tempfd, &ipcsrv_allset);

         // set new max fd if required

#ifdef OS_WIN32
         if ((int)tempfd > ipcsrv_maxfd)
            ipcsrv_maxfd = (int)tempfd;
#else
         if (tempfd > ipcsrv_maxfd)
            ipcsrv_maxfd = tempfd;
#endif

         // set new index into 'client' array if required

         if (i > ipcsrv_maxi)
            ipcsrv_maxi = i;

         if (--nready <= 0)
            continue;
         }

      // check all clients for data

      for(i = 0; i <= ipcsrv_maxi; i++)
         {
         if ((srvclientSocket = ipcsrv_client[i]) == INVALID_SOCKET)
            continue;

         if (FD_ISSET(srvclientSocket, &ipcsrv_rset))
            {
            // call server module to read request, process and send reply

            ret = process_request();

            /* 'process_request' returns zero normal, one on termination
               request and -1 on socket error */

            switch(ret)
               {
               case 1:
                  // close all sockets upon termination
                  (void)ipc_close();
                  return;
                  break;

               case -1:
                  log_file_date("%s:connection closed by client", mname);
                  (void)ipc_close_client();
                  FD_CLR(srvclientSocket, &ipcsrv_allset);
                  ipcsrv_client[i] = INVALID_SOCKET;
                  break;

               default:
                  ipcsrv_trans_no++;
               };

            if (--nready <= 0)
               break;
            }
         }
      }
}
#endif

#ifdef IPC_TCP
int ipc_recv_data(char *buf)
#else
int ipc_recv_data(char *buf, int size_buf)
#endif
{
   /* Receive server data into 'buf' which must be allocated
      to sufficient size. Function returns the number of
      bytes read upon success, zero otherwise. The QNX
      message passing variant will read block in this
      function until a message is received. */
     
#ifdef IPC_QNX
   if ((server_pid = Receive(0, buf, size_buf)) == -1)
      return(0);
   
   return(strlen(buf));
#else
   return(ipc_recv(srvclientSocket, buf));
#endif
}

int ipc_send_data(char *buf)
{
   /* Send client data. All data in 'buf' will be sent.
      Function returns the number of bytes sent upon
      success, zero otherwise. */

#ifdef IPC_QNX
   if (Reply(server_pid, buf, strlen(buf) + 1) == -1)
      return(0);

   return(strlen(buf));
#else
   return(ipc_send(srvclientSocket, buf));
#endif
}

/* following functions apply to TCP IPC only */

#ifdef IPC_TCP
int ipc_close_client(void)
{
   /* Close the client socket in use by the server.
      Function returns 'TRUE' upon success, 'FALSE'
      otherwise. */

   if (srvclientSocket == INVALID_SOCKET)
      return(FALSE);

#ifdef OS_WIN32
   if (closesocket(srvclientSocket) == SOCKET_ERROR)
      return(FALSE);
#endif

#ifdef OS_UNIX
   close(srvclientSocket);
#endif

   // srvclientSocket = INVALID_SOCKET;
   return(TRUE);
}

int ipc_close(void)
{
   /* Close all sockets in use by the server.
      Function returns 'TRUE' upon success, 'FALSE'
      otherwise. */

   int i;
#ifdef OS_WIN32
   SOCKET tempfd;
#else
   int tempfd;
#endif

   for(i = 0; i <= ipcsrv_maxi; i++)
      {
      if ((tempfd = ipcsrv_client[i]) == INVALID_SOCKET)
         continue;

#ifdef OS_WIN32
      (void)closesocket(tempfd);
#endif

#ifdef OS_UNIX
      close(tempfd);
#endif
      }

#ifdef OS_WIN32
   if (closesocket(serverSocket) == SOCKET_ERROR)
      return(FALSE);
#endif

#ifdef OS_UNIX
   close(serverSocket);
#endif

   return(TRUE);
}

long ipc_trans_num(void)
{
   // Return the current transaction count (TCP only).

   return(ipcsrv_trans_no);
}

int ipc_connect_num(void)
{
   // Return the current number of clients connected (TCP only).

   int i, connect_count;

   for(i = connect_count = 0; i < FD_SETSIZE; i++)
      if (ipcsrv_client[i] != INVALID_SOCKET)
         connect_count++;

   return(connect_count);
}

void ipc_display_client(void)
{
   // Display client socket pointer. For debuging.

#ifdef OS_WIN32
   printf("ipc_display_client:%p\n", (void *)srvclientSocket);
#endif

#ifdef OS_UNIX
   printf("ipc_display_client:%d\n", srvclientSocket);
#endif
}
#endif
