/*  $Id: io.c,v 1.3 2001/03/02 09:59:24 remlali Exp $
 *
 * Copyright 2000
 * tcp_service.c -- create requests from socket
 *
 *  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, write to the Free Software
 *  Foundation, Inc.,  59 Temple Place - Suite 330, Cambridge, MA 02139, USA.
 *
 *  io.c -- a IPC wrapper ontop of glib IOChannel
 *
 * This file is based on database/server/tcp_service.c
 * Try replace tcp_service with this and dd_io2.c file
 *
 * 20010216 OK, this file is from traps/lib/dd_io.c
 *          traps/lib/dd_io.c should use this one!
 *
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <glib.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>

/****************************************************************************
 * Forward references
 ***************************************************************************/
gboolean             io_new_connection            (GIOChannel     *source,
						   GIOCondition   condition,
						   gpointer       data);
gboolean             io_peer_activity             (GIOChannel     *source,
						   GIOCondition   condition,
                                                   gpointer       data);
/****************************************************************************
 * Implementation.
 ***************************************************************************/

void
io_enable_reuseaddr (gint sock)
{
  gint tmp = 1;
  if (sock < 0)
    return;
  if (setsockopt (sock, SOL_SOCKET, SO_REUSEADDR, (gchar *)&tmp, 
		  sizeof (tmp)) == -1)
    perror ("Bah! Bad setsockopt ()\n");
}

void 
io_enable_nbio (gint fd)
{
  if (fcntl (fd, F_SETOWN, getpid()) == -1)
    perror ("fcntl (F_SETOWN) error\n");
/*  //FNDELAY NOT SUPPORTED BY SOLARIS
  if (fcntl (fd, F_SETFL, FNDELAY) == -1)
    perror ("fcntl (F_SETFL, FNDELAY\n");
*/
}

void
io_connect_as_server (gint port, gpointer func,
                                 gpointer data)
{
static  gint                s;
static  struct sockaddr_in  addr;
static  GIOChannel          *channel;

  if (port <= 0)
    return ;

  s = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);
  if (s == -1)
    return;
  io_enable_reuseaddr (s);
  memset (&addr, 0, sizeof (addr));
  addr.sin_family       = AF_INET;
  addr.sin_port         = htons ((u_short)port);
  addr.sin_addr.s_addr  = INADDR_ANY;
  if (bind (s, (struct sockaddr *)&addr, sizeof (addr)) == -1)
    {
      close (s);
      return ;
    }
  if (listen (s, 5) == -1)
    {
      close (s);
      return ;
    }
  channel = g_io_channel_unix_new (s);
  g_io_add_watch (channel, G_IO_IN, io_new_connection, func);
}

GIOChannel *
io_connect_as_client_public (gchar *address, gint port, 
		      gboolean (* activity_func)(), gpointer data)
{
static  gint     sock;
static  struct sockaddr_in ddserver;   /* ick */
static  GIOChannel          *channel;
  
  sock = socket (PF_INET, SOCK_STREAM, 0);
  if (sock < 0)
    return -1;

  ddserver.sin_family = AF_INET;
  ddserver.sin_port   = htons(port);
  ddserver.sin_addr.s_addr = inet_addr (address);
  if (0 > connect (sock, (struct sockaddr *) &ddserver, sizeof (ddserver)))
    return -1;

  channel = g_io_channel_unix_new (sock);
  g_io_add_watch (channel, G_IO_IN, io_peer_activity, activity_func);

  return channel;
}

GIOChannel *
io_connect_as_client_private (gchar *address, gint port, 
		      gboolean (* activity_func)(), gpointer data)
{
static  gint     sock;
static  struct sockaddr_in ddserver;   /* ick */
static  GIOChannel          *channel;
  
  sock = socket (PF_INET, SOCK_STREAM, 0);
  if (sock < 0)
    return -1;

  ddserver.sin_family = AF_INET;
  ddserver.sin_port   = htons(port);
  ddserver.sin_addr.s_addr = inet_addr (address);
  if (0 > connect (sock, (struct sockaddr *) &ddserver, sizeof (ddserver)))
    return -1;

  channel = g_io_channel_unix_new (sock);
  g_io_add_watch (channel, G_IO_IN, activity_func, data);

  return channel;
}

/****************************************************************************
 * Callback functions
 ***************************************************************************/
/**
 * io_new_connection 
 * Will accept a new connection and add the client to the list of clients.
 * actual client communication is handled elsewhere.
 **/
gboolean 
io_new_connection (GIOChannel *source, GIOCondition cond, gpointer data)
{
  gint new;              /* new socket descriptor */
  gint               client;
  GIOChannel *new_channel;
static  struct sockaddr_in client_addr;
static  gboolean (* user_func)();
static  gint fd;
  
  user_func = data;


  if (cond == G_IO_IN)
    {
      fd = g_io_channel_unix_get_fd(source);
      if ( (new = accept (fd,
			  (struct sockaddr *)&client_addr, &client)) < 0)
        {
          fprintf(stderr,"errno(%u)\n", errno);
	  g_warning ("Unable to accept new connection.");
	  return FALSE;
        }
      new_channel = g_io_channel_unix_new (new);

      /**** Connection Register Callback ****/
      user_func (0, new_channel, 0, 0);
      /******************************/
      g_io_add_watch (new_channel, G_IO_IN, io_peer_activity, data);
    }
  return TRUE;
}

/**
 * io_peer_activity
 * Handles input from the clients and passes it off to the parser/dispatcher.
 * This will get the raw data from the socket, make sure there is a NULL 
 * terminator, and call the command dispatcher.
 * Notice we let user_func decide wheter to drop or leave event (TRUE/FALSE return).
 **/
gboolean 
io_peer_activity (GIOChannel *source, GIOCondition cond, 
                     gpointer data)
{
  guint num_read = 0;
  gchar bufme[40000];
  gboolean (* user_func)();

  user_func = data;

  if (cond == G_IO_IN)
    {
      switch(g_io_channel_read (source, bufme, sizeof (bufme), &num_read))
        {
          case G_IO_ERROR_NONE:
            if(!num_read){
              return user_func (2, source, 0, 0);
            }
            return user_func (1, source, num_read, bufme);
          default:
            fprintf(stderr,"io.c: G_IO_ERROR\n");
        }
    }
  else
    {
      fprintf(stderr, "io.c: UNKNOWN CONDITION\n");
    }
  return TRUE;
}

/**
 * io_server_peer_disconnect()
 * Called when A client disappears, should remove it from the list and
 * go on with life.
 *
 **/
gboolean
io_server_peer_disconnect (GIOChannel *source, GIOCondition cond, gpointer data)
{
  GList *gl;

  g_io_channel_close(source);
  return TRUE;
}
