/*
   <h3>Functions to manage pollable files</h3>

   <p>This package contains a set of functions to manipulate
   a list of pollable fds. It will also manage the
   associated threads if the calling program is a
   multi-threaded design based round a single <i>poll()</i>
   based thread dispatcher.</p>

   <p>All the information required by the package is kept in a single
   instance of the <b>FDL</b> structure. There is one one of these
   in each running program. It contains parallel arrays of <b>pollfd</b>s,
   listener flags and <b>TCB</b> structures. A <b>TCB</b> holds details of
   an active thread. Elements in the arrays are found be using the 
   <i>fd</i> of an open file or socket as the key. Only one entry in the
   listener array is TRUE: this identifies the <i>listener</i> fd.
   The size of the arrays are set at compile time by the <b>MAXFD</b>
   constant, which is currently set to 50.</p>

   <p><b>NOTE:</b> Don't change <b>MAXFD</b> without recompiling the
                   package.</p>

   <p>The contents of the <b>FDL</b> structure should not be accessed directly
   by the calling program: use the <i>fdlist_xxxx()</i> functions for all
   manipulation of its contents.</p>

   <p>This collection of functions is compiled to form part
   of the <b>libenviron.a</b> library, which must be linked
   if any of of its constituent functions are called.</p>

   <h3>Compilation</h3>
        
   <p>The calling programs should include <i>fdlist.h</i> and must be
   linked using <i>-lenviron</i>.</p>

   <kbd>
   <b>environ - C programming support functions</b><br>
   Copyright (C) 2005  Martin C Gregorie
   
   This program is free software; you can redistribute it and/or
   modify it under the terms of the Lesser GNU General Public License.
   
   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 Lesser GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
   </kbd>
  
   <p>Contact Martin Gregorie at <i>martin@gregorie.org</i></p>
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <poll.h>
#include <pthread.h>
#include <semaphore.h>
#include "fdlist.h"

/*
   Private global variables
*/
static int debug = 0;

/*
   Private function prototypes.
*/
void           *fdlist_malloc(size_t n);

/*
   This function sets debugging level for the package.
   A setting of zero turns tracing off. A value of 1 turns
   on function call tracing as an aid to debugging the calling
   program. Values of 2 or greater turn on a limited amount of
   internal function tracing.

   <b>NOTE:</b> Calls to <i>fdlist_fds()</i> and <i>fdlist_size()</i>
   are not traced below a debug level of 2 because this output can
   be confusing when the calling program is being debugged.
*/
void fdlist_debug(int d)
{
   if (d < 0 )
      d = 0;
      
   debug = d;

   if (debug)
      logmsg("fdlist_debug(%d)", debug);
}

/*
   Create and initialise the FDL structure.
   Returns a pointer to the structure on success and
   null on error. Failure to acquire memory to hold
   the FDL structure or its components will cause the
   program to abandon with a message that it can't
   allocate the required amount of memory.
*/
FDL *fdlist_create(void)
{
   FDL   *x;
   int   i;

   x = (FDL*)malloc(sizeof(FDL));
   if (x)
   {
      x->count = 0;
      for (i = 0; i < MAXFDS; i++)
      {
         x->fds[i].fd = -1;
         x->fds[i].events = 0;
         x->fds[i].revents = 0;
         x->listener[i] = CONNECTION;
         x->tcb[i].fd = -1;
         x->tcb[i].sem_id = fdlist_malloc(sizeof(sem_t));
         x->tcb[i].thread_id = fdlist_malloc(sizeof(pthread_t));
         x->tcb[i].debug = debug;
      }
   }

   if (debug)
      logmsg("%p = fdlist_create()", x);

   return x;
}

/*
   Deallocate the FDL structure and release its storage space.
   It returns null.
*/
FDL *fdlist_release(FDL *pl)
{
   FDL   *x = NULL;
   int   i;

   for (i = 0; i < MAXFDS; i++)
   {
      free(pl->tcb[i].sem_id);
      free(pl->tcb[i].thread_id);
   }

   free(pl);
   if (debug)
      logmsg("%p = fdlist_release(%p)", x, pl);

   return x;       
}

/*
   Add a file fd to the list of polled files.
   <u>listener_flag</u> is set to be TRUE if this file is the
   socket listener for this process. It returns ADDED on success,
   ON_LIMIT if the fd could not be added because the <b>FDL</b>
   arrays are full, or ERROR if some other problem ocurred.
*/
int fdlist_add(FDL *pl, int fd, int listener_flag)
{
   int   i;
   int   reply = ERROR;
   char  *s = "ERROR";

   if (listener_flag == LISTENER || listener_flag == CONNECTION)
   {   
      if (pl)
      {
         for (i = 0; i < pl->count; i++)
         {
            if (pl->fds[i].fd == -1)
            {
               pl->fds[i].fd = fd;
               pl->fds[i].events = POLLIN;
               pl->fds[i].revents = 0;
               pl->listener[i] = listener_flag;
               break;   
            }
         }

         if (i >= pl->count)
         {
            if (i < MAXFDS)
            {
               pl->count = i + 1;
               pl->fds[i].fd = fd;
               pl->fds[i].events = POLLIN;
               pl->fds[i].revents = 0;
               pl->listener[i] = listener_flag;
               reply = ADDED;
            }
            else
               reply = ON_LIMIT;
         }
         else
            reply = ADDED;
      }  
   }
   
   if (debug)
   {
      if (reply == ADDED)
         s = "ADDED";

      if (reply == ON_LIMIT)
         s = "ON_LIMIT";
         
      logmsg("%s = fdlist_add(%p, %d, %s) - in slot %d. %d slots exist",
             s,
             pl,
             fd,
             (listener_flag == LISTENER ? "LISTENER" : "CONNECTION" ),
             i,
             pl->count);
   }
   
   return reply;
}

/*
   Remove a file fd from the fd list.
   Returns -1 on error or zero on success.
*/
int fdlist_del(FDL *pl, int fd)
{
   int   i;
   int   reply = -1;

   if (pl)
   {
      for (i = 0; i < pl->count; i++)
      {
         if (pl->fds[i].fd == fd)
         {
            pl->fds[i].fd = -1;
            pl->fds[i].events = 0;
            pl->fds[i].revents = 0;
            pl->listener[i] = CONNECTION;
            break;   
         }
      }

      if (i < pl->count)
         reply = 0;
   }

   if (debug)
      logmsg("%d = fdlist_del(%p, %d) - from slot %d. %d slots exist",
             reply,
             pl,
             fd,
             i,
             pl->count);

   return reply;
}

/*
   Retrieve the event flags mask for an existing fd list entry.
   <u>*evflags</u> points to a short that will receive
   the flags set by the current event for <u>fd</u>.
   Returns -1 on error or zero on success.
*/
int fdlist_getevents(FDL *pl, int fd, short *evflags)
{
   int   i;
   int   reply = -1;

   if (pl)
   {
      for (i = 0; i < pl->count; i++)
      {
         if (pl->fds[i].fd == fd)
         {
            *evflags = pl->fds[i].events;
            break;   
         }
      }

      if (i < pl->count)
         reply = 0;
   }

   if (debug)
      logmsg("%d = fdlist_getevents(%p, %d, %s) - slot %d. %d slots exist",
             reply,
             pl,
             fd,
             fdlist_decode(*evflags),
             i,
             pl->count);

   return reply;
}

/*
   Set the event flags mask in an existing fd list
   entry. Used primarily to turn the POLLOUT flag
   on when data is available to write and off
   again when it is exhausted. The value in <u>evflags</u>
   replaces the event flag mask for the specified <u>fd</u>.
   Returns -1 on error or zero on success.
*/
int fdlist_setevents(FDL *pl, int fd, short evflags)
{
   int   i;
   int   reply = -1;

   if (pl)
   {
      for (i = 0; i < pl->count; i++)
      {
         if (pl->fds[i].fd == fd)
         {
            pl->fds[i].events = evflags;
            break;   
         }
      }

      if (i < pl->count)
         reply = 0;
   }

   if (debug)
      logmsg("%d = fdlist_setevents(%p, %d, %s) - slot %d. %d slots exist",
             reply,
             pl,
             fd,
             fdlist_decode(evflags),
             i,
             pl->count);

   return reply;
}

/*
   Returns TRUE if the fd is the socket listener for this process.
*/
int fdlist_islistener(FDL *pl, int fd)
{
   int   i;
   int   reply = FALSE;

   if (pl)
   {
      for (i = 0; i < pl->count; i++)
      {
         if (pl->fds[i].fd == fd)
         {
            reply = pl->listener[i];
            break;   
         }
      }
   }

   if (debug)
      logmsg("%s = fdlist_islistener(%p, %d)",
             (reply == LISTENER ? "LISTENER" : "CONNECTION"),
             pl,
             fd);

   return reply;
}

/*
   Returns an fd if there is an open file in the list
   that is not the listener. Returns -1 if there isn't
   any such file.

   This is primarily used during a forced shutdown to find all open
   sockets and close them in an orderly fashion. It should be called
   repeatedly until it returns -1, dealing with each open file in turn.
*/
int fdlist_nextopen(FDL *pl)
{
   int   i;
   int   reply = -1;

   if (pl)
   {
      for (i = 0; i < pl->count; i++)
      {
         if (pl->fds[i].fd >= 0 && pl->listener[i] == CONNECTION)
         {
            reply = pl->fds[i].fd;
            break;   
         }
      }
   }

   if (debug)
      logmsg("%d = fdlist_nextopen(%p)", reply, pl);

   return reply;
}

/*
   Find an fd that caused <i>poll()</i> to exit.
   Returns -1 if no file has events notified or the fd is invalid.

   After <i>poll()</i> has exited this function should be called
   repeatedly until it returns -1. This will find each file that
   caused <i>poll()</i> to exit. It is the central run-time function
   in this package:
   <pre>
   poll(fdlist_fds(fdl), fdlist_size(fdl), timeout);
   while ((fd = fdlist_getfd(fdl)) != -1)
   {
      events = fdlist_geteventtype(fdl, fd);
      ...process the events....
   }
   </pre>
   The above, shorn of its error handling, shows how a number
   of the <i>fdlist_xxx()</i> functions should be used.
*/
int fdlist_getfd(FDL *pl)
{
   int   i;
   int   reply = -1;

   if (pl)
   {
      for (i = 0; i < pl->count; i++)
      {
         if (pl->fds[i].fd != -1 && pl->fds[i].revents)
         {
            reply = pl->fds[i].fd;
            if (debug > 1)
            {
               logmsg("Events for fd %d: %s",
                      reply,
                      fdlist_decode(pl->fds[i].revents));
            }

            break;   
         }
      }
   }

   if (debug)
      logmsg("%d = fdlist_getfd(%p) - from slot %d. %d slots exist",
             reply,
             pl,
             i,
             pl->count);

   return reply;
}

/*
   Retrieve the current event list for the fd in order to see why
   it caused <i>poll()</i> to exit. The events are cleared in
   preparation for the next call on <i>poll()</i>.

   A return value of zero means that either the fd does not
   match anything in the FDL or that <u>fd</u> refers to a
   file that has not raised any events.
*/
short fdlist_geteventtype(FDL *pl, int fd)
{
   int   i;
   short reply = 0;

   if (pl)
   {
      for (i = 0; i < pl->count; i++)
      {
         if (pl->fds[i].fd == fd)
         {
            reply = pl->fds[i].revents;
            pl->fds[i].revents = 0;
            break;   
         }
      }
   }

   if (debug)
      logmsg("0x%04x (%s) = fdlist_geteventtype(%p, %d)",
             reply,
             fdlist_decode(reply),
             pl,
             fd);

   return reply;
}

/*
   Retrieve a pointer to the thread
   control block for the given fd.
   Returns the pointer on success and null if
   the fd could not be found.
*/
TCB *fdlist_get_tcb_ref(FDL *pl, int fd)
{
   int   i;
   TCB   *t = NULL;

   if (pl)
   {
      for (i = 0; i < pl->count; i++)
      {
         if (pl->fds[i].fd == fd)
         {
            t =  &(pl->tcb[i]);
            break;   
         }
      }
   }

   if (debug)
      logmsg("%p = fdlist_get_tcb_ref(%p, %d)",
             t,
             pl,
             fd);

   return t;
}

/*
   Return a pointer to the list of fds for use by <i>poll()</i>.
   Returns null if the operation failed: this will be because the
   FDL structure has not been created.

   This function is not traced below a debugging level of 2.
*/
struct pollfd *fdlist_fds(FDL *pl)
{
   struct pollfd *x;

   if (pl)
      x = pl->fds;
   else
      x = NULL;
      
   if (debug > 1)
      logmsg("%p = fdlist_fds(%p)", x, pl);

   return x;
}

/*
   Return the number of allocated fds for use by <i>poll()</i>.
   It returns -1 if the FDL structure has not been created.

   This function is not traced below a debugging level of 2.
*/
int fdlist_size(FDL *pl)
{
   int size;

   if (pl)
      size = pl->count;
   else
      size = -1;
 
   if (debug > 1)
      logmsg("%d = fdlist_size(%p)", size, pl);

   return size;
}

/*
   Decode the event flags in <u>revents</u> and return a string
   containing the result. This is used internally by <i>fdlist_xxx()</i>
   functions that return or manipulate event flags.
   It is a diagnostic tool.
*/
char *fdlist_decode(short revents)
{
   static char buff[50];

   strcpy(buff, "");
   if (revents & POLLIN)
      strcat(buff, "POLLIN ");
   if (revents & POLLPRI)
      strcat(buff, "POLLPRI ");
   if (revents & POLLOUT)
      strcat(buff, "POLLOUT ");
   if (revents & POLLERR)
      strcat(buff, "POLLERR ");
   if (revents & POLLHUP)
      strcat(buff, "POLLHUP ");
   if (revents & POLLNVAL)
      strcat(buff, "POLLNVAL ");

   if (strlen(buff) == 0)
      strcpy(buff, "no flags set");

   return(buff);      
}

/*
   Only used by functions in this source file.
   It provides an error trap for malloc().
*/ 
void* fdlist_malloc(size_t n)
{
   void *p;
   char dec[10];

   p = malloc(n);
   if (p ==NULL)
      error(progname(), "can't allocate memory. Size=", itoa(n, dec, 10));

   return p;
}
