/***************************************************************************
 *
 * Copyright (c) 2000, 2001 BalaBit IT Ltd, Budapest, Hungary
 *
 * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * $Id: poll.c,v 1.20.4.2 2001/09/19 19:22:16 bazsi Exp $
 *
 * Author  : Bazsi
 * Auditor : kisza
 * Last version: 1.11
 * Notes:
 *   - no process identifier at messages
 *
 ***************************************************************************/

#include <zorp/poll.h>
#include <zorp/stream.h>
#include <zorp/log.h>
#include <sys/types.h>
#include <sys/poll.h>
#include <errno.h>
#include <unistd.h>
#include <assert.h>

#define CORE_DEBUG "core.debug"
#define CORE_ERROR "core.error"

/*+

  ZRealPoll is an implementation of the ZPoll interface defined in
  poll.h. It's a callback based poll loop, which can be used by proxy
  modules. It holds a collection of ZStream objects and calls its
  callbacks when a requested I/O event occurs. ZPoll objects are
  reference counted and are automatically freed when the number of
  references to a single instance reaches zero.

  +*/
typedef struct _ZRealPoll
{
  guint ref_count;
  ZStream *streams;
  gboolean quit;
  GStaticMutex lock;
  int wakeup_pipe[2];
} ZRealPoll;

/*+

  Creates a new ZPoll instance.

  Parameters:
    none

  Returns:
    new instance

  +*/
ZPoll *
z_poll_new(void)
{
  ZRealPoll *self = g_new0(ZRealPoll, 1);
  
  self->ref_count = 1;
  self->quit = FALSE;
  if (pipe(self->wakeup_pipe) < 0)
    {
      g_free(self);
      return NULL;
    }
  return (ZPoll *) self;
}

/*+

  Used internally to free up an instance when the reference count
  reaches 0.

  Parameters:
    s         instance pointer

  Returns:
    none

  +*/
static void
z_poll_destroy(ZPoll *s)
{
  ZRealPoll *self = (ZRealPoll *) s;
  ZStream *p, *next_p;
  
  close(self->wakeup_pipe[0]);
  close(self->wakeup_pipe[1]);
  for (p = self->streams; p; p = next_p)
    {
      next_p = p->next;
      z_stream_unref(p);
    }  
  g_free(self);
}

/*+

  Increment the reference count of the given ZPoll instance.

  Parameters:
    s            ZPoll instance

  Returns: 
    none

  +*/
void 
z_poll_ref(ZPoll *s)
{
  ZRealPoll *self = (ZRealPoll *) s;
  self->ref_count++;
}


/*+

  Decrement the reference count of the given ZPoll instance, and free
  it using z_poll_destroy() if it reaches 0.

  Parameters:
    s           ZPoll instance

  Returns:
    none

  +*/
void
z_poll_unref(ZPoll *s)
{
  ZRealPoll *self = (ZRealPoll *) s;

  if (self)
    {
      self->ref_count--;
      if (self->ref_count == 0)
        z_poll_destroy(s);
    }
}

/*+

  Register a ZStream to be monitored by a ZPoll instance.

  Parameters:
    s          ZPoll instance
    stream     stream to register

  Returns:
    none

  +*/
void
z_poll_add_stream(ZPoll *s, ZStream *stream)
{
  ZRealPoll *self = (ZRealPoll *) s;

  g_io_channel_ref(&stream->super);
  stream->next = self->streams;
  self->streams = stream;
}

/*+

  Remove a ZStream from a ZPoll instance.

  Parameters:
    s          ZPoll instance
    stream     stream to remove

  Returns:
    none

  +*/
void
z_poll_remove_stream(ZPoll *s, ZStream *stream)
{
  ZRealPoll *self = (ZRealPoll *) s;
  ZStream *tmpstream;

  g_io_channel_unref(&stream->super);
  
  tmpstream = self->streams;
  if (tmpstream == stream)
    {
      self->streams = tmpstream->next;
    }
  else
    {
      while ( tmpstream && tmpstream->next && tmpstream->next != stream )
        tmpstream = tmpstream->next;
  
      if (tmpstream && tmpstream->next)
        {
          tmpstream->next = tmpstream->next->next;
        }
    }
}

/*+

  Run an iteration of the poll loop. Monitor filedescriptors of
  registered Streams, and call appropriate callbacks.

  Parameters:
    s          ZPoll instance
    timeout    poll timeout in milliseconds
 
  Returns:
    TRUE if the iteration should be ended.

  +*/
guint
z_poll_iter_timeout(ZPoll *s, gint timeout)
{
  ZRealPoll *self = (ZRealPoll *) s;
  ZStream *stream;
  struct pollfd *pollfds;
  ZStream **pollstreams;
  guint i, npollfds = 0;
  gint res;

  errno = 0;
  if (self->quit)
    return 0;
  
  for (stream = self->streams; stream; stream = stream->next)
    {
      if (stream->want_read || stream->want_write || stream->want_pri)
        {
          npollfds++;
        }
    }
    
  if (!npollfds)
    {
      /*LOG
        This message is sent when a poll object has no pending file descriptors to watch. 
        It usually indicates an internal error.
       */
      z_log(NULL, CORE_ERROR, 4, "No polled fds, exiting;");
      return 0;
    }
  npollfds++;
  pollfds = (struct pollfd *) alloca(npollfds * sizeof(GPollFD));
  pollstreams = (ZStream **) alloca(npollfds * sizeof(ZStream *));

  pollfds[0].fd = self->wakeup_pipe[0];
  pollfds[0].events = POLLIN;
  pollfds[0].revents = 0;
  pollstreams[0] = NULL;
  for (i = 1, stream = self->streams; stream; stream = stream->next)
    {
      if (stream->want_read || stream->want_write || stream->want_pri)
        {
          assert(npollfds >= i);

          pollstreams[i] = stream;
          pollfds[i].fd = stream->fd;
          pollfds[i].events = 0;
          if (stream->want_read)
            pollfds[i].events = POLLIN | POLLRDNORM | POLLHUP;
          if (stream->want_write)
            pollfds[i].events |= POLLOUT | POLLWRNORM;
          if (stream->want_pri)
            pollfds[i].events |= POLLPRI;
          pollfds[i].revents = 0;
#if 0
          /*NOLOG*/
	  z_log(NULL, CORE_DEBUG, 8, "Preparing fd; i=%d, fd=%d, events=0x%04x", i, pollfds[i].fd, pollfds[i].events);
#endif          
          i++;
        }
    }
  res = poll(pollfds, npollfds, timeout);
  
  if (pollfds[0].revents != 0)
    {
      char buf[1];
      
      read(self->wakeup_pipe[0], buf, sizeof(buf));
      z_trace(NULL, "poll woken up; self='%p'", self);
      return 2;
    }
  if (res < 0)
    {
      switch (errno)
        {
        case EINTR:
          return 1;
        default:
          /*LOG
            This message indicates an error condition occuring during a poll(2)
            system call.
           */
          z_log(NULL, CORE_ERROR, 3, "Error during poll(); error='%m'");
          return 0;
        }
    }
  else if (res == 0)
    {
      /*LOG
        This message indicates that the poll(2) system call timed out without
        any of the file descriptors getting active.
       */
      z_log(NULL, CORE_ERROR, 4, "Poll timed out;");
      errno = ETIMEDOUT;
      return 0;
    }
  else 
    {
      for (i = 0; i < npollfds; i++) 
	{
#if 0
          /*NOLOG*/
	  z_log(NULL, CORE_DEBUG, 8, "process: i=%d, fd=%d, events=0x%04x, revents=0x%04x", i, pollfds[i].fd, pollfds[i].events, pollfds[i].revents);
#endif
	  if (pollfds[i].revents)
	    {
	      res = 0;
	      if (pollfds[i].revents & POLLHUP)
	        {
	          if (pollstreams[i]->want_read && pollstreams[i]->read)
	            res = ZSTREAM_READ(pollstreams[i], pollfds[i].revents);
	          else if (pollstreams[i]->want_write && pollstreams[i]->write)
	            res = ZSTREAM_WRITE(pollstreams[i], pollfds[i].revents);
	          else
	            {
	              /*LOG
	                This message indicates that the system call poll() indicates
	                a broken connection on the given fd, but Zorp didn't request
	                that. This may either indicate an internal error, or some kind
	                of interoperability problem between your operating system
	                and Zorp.
	               */
 	              z_log(NULL, CORE_ERROR, 4, "POLLHUP was received on an inactive fd; fd='%d'", pollfds[i].fd);
 	              z_stream_close(pollstreams[i]);
 	            }
	        }
	      if (pollfds[i].revents & POLLPRI)
	        {
	          if (pollstreams[i]->want_pri && pollstreams[i]->pri)
	            res = ZSTREAM_PRI(pollstreams[i], pollfds[i].revents);
	          else
	            {
	              /*LOG
	                This message indicates that the system call poll() indicates
	                the availibility of out-of-band data, but Zorp didn't request that.
	                This may either indicate an internal error, or some kind
	                of interoperability problem between your operating system and
	                Zorp.
	               */
	              z_log(NULL, CORE_ERROR, 4, "Unrequested OOB data available; fd='%d'", pollfds[i].fd);
	              z_stream_close(pollstreams[i]);
	            }
	        }
	      if (pollfds[i].revents & POLLOUT)
	        {
	          res |= ZSTREAM_WRITE(pollstreams[i], pollfds[i].revents);
	        }
	      if (pollfds[i].revents & POLLIN)
	        {
	          res |= ZSTREAM_READ(pollstreams[i], pollfds[i].revents);
	        }

	      switch (ST_ACTION(res))
	        {
	        case ST_GOON:
	          break;
	        case ST_CLOSE:
	          z_stream_close(pollstreams[i]);
	          break;
	        case ST_QUIT:
	          return 0;
	        }
            }
	}
    }
  return 1;
}

void
z_poll_wakeup(ZPoll *s)
{
  ZRealPoll *self = (ZRealPoll *) s;
  
//  g_static_mutex_lock(&self->lock);
//  if (self->poll_running)
//    {
      write(self->wakeup_pipe[1], "X", 1);
//    }
//  g_static_mutex_unlock(&self->lock);
}
