/*
 * File: IO.c
 *
 * Copyright (C) 2000-2007 Jorge Arellano Cid <jcid@dillo.org>
 *
 * 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 3 of the License, or
 * (at your option) any later version.
 */

/*
 * Dillo's event driven IO engine
 */

#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include "../msg.h"
#include "../chain.h"
#include "../klist.h"
#include "IO.h"
#include "iowatch.hh"

/*
 * Symbolic defines for shutdown() function
 * (Not defined in the same header file, for all distros --Jcid)
 */
#define IO_StopRd   1
#define IO_StopWr   2
#define IO_StopRdWr (IO_StopRd | IO_StopWr)


typedef struct {
   int Key;               /* Primary Key (for klist) */
   int Op;                /* IORead | IOWrite */
   int FD;                /* Current File Descriptor */
   int Flags;             /* Flag array (look definitions above) */
   int Status;            /* errno code */
   Dstr *Buf;             /* Internal buffer */

   void *Info;            /* CCC Info structure for this IO */
} IOData_t;


/*
 * Local data
 */
static Klist_t *ValidIOs = NULL; /* Active IOs list. It holds pointers to
                                  * IOData_t structures. */

/*
 *  Forward declarations
 */
void a_IO_ccc(int Op, int Branch, int Dir, ChainLink *Info,
              void *Data1, void *Data2);


/* IO API  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

/*
 * Return a new, initialized, 'io' struct
 */
static IOData_t *IO_new(int op)
{
   IOData_t *io = dNew0(IOData_t, 1);
   io->Op = op;
   io->FD = -1;
   io->Flags = 0;
   io->Key = 0;
   io->Buf = dStr_sized_new(IOBufLen);

   return io;
}

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

/*
 * Register an IO in ValidIOs
 */
static void IO_ins(IOData_t *io)
{
   if (io->Key == 0) {
      io->Key = a_Klist_insert(&ValidIOs, io);
   }
   _MSG("IO_ins: io->Key=%d, Klist_length=%d\n",
       io->Key, a_Klist_length(ValidIOs));
}

/*
 * Remove an IO from ValidIOs
 */
static void IO_del(IOData_t *io)
{
   if (io->Key != 0) {
      a_Klist_remove(ValidIOs, io->Key);
   }
   io->Key = 0;
   _MSG(" -->ValidIOs: %d\n", a_Klist_length(ValidIOs));
}

/*
 * Return a io by its Key (NULL if not found)
 */
static IOData_t *IO_get(int Key)
{
   return (IOData_t *)a_Klist_get_data(ValidIOs, Key);
}

/*
 * Free an 'io' struct
 */
static void IO_free(IOData_t *io)
{
   dStr_free(io->Buf, 1);
   dFree(io);
}

/*
 * Close an open FD, and remove io controls.
 * (This function can be used for Close and Abort operations)
 * BUG: there's a race condition for Abort. The file descriptor is closed
 * twice, and it could be reused for something else in between. It's simple
 * to fix, but it'd be better to design a canonical way to Abort the CCC.
 */
static void IO_close_fd(IOData_t *io, int CloseCode)
{
   int st;
   int events = 0;

   _MSG("====> begin IO_close_fd (%d) Key=%d CloseCode=%d Flags=%d ",
       io->FD, io->Key, CloseCode, io->Flags);

   /* With HTTP, if we close the writing part, the reading one also gets
    * closed! (other clients may set 'IOFlag_ForceClose') */
   if (((io->Flags & IOFlag_ForceClose) || (CloseCode == IO_StopRdWr)) &&
       io->FD != -1) {
      do
         st = close(io->FD);
      while (st < 0 && errno == EINTR);
   } else {
      _MSG(" NOT CLOSING ");
   }
   /* Remove this IOData_t reference, from our ValidIOs list
    * We don't deallocate it here, just remove from the list.*/
   IO_del(io);

   /* Stop the polling on this FD */
   if (CloseCode & IO_StopRd) {
     events |= DIO_READ;
   }
   if (CloseCode & IO_StopWr) {
     events |= DIO_WRITE;
   }
   a_IOwatch_remove_fd(io->FD, events);
   _MSG(" end IO close (%d) <=====\n", io->FD);
}

/*
 * Read data from a file descriptor into a specific buffer
 */
static bool_t IO_read(IOData_t *io)
{
   char Buf[IOBufLen];
   ssize_t St;
   bool_t ret = FALSE;
   int io_key = io->Key;

   _MSG("  IO_read\n");

   /* this is a new read-buffer */
   dStr_truncate(io->Buf, 0);
   io->Status = 0;

   while (1) {
      St = read(io->FD, Buf, IOBufLen);
      if (St > 0) {
         dStr_append_l(io->Buf, Buf, St);
         continue;
      } else if (St < 0) {
         if (errno == EINTR) {
            continue;
         } else if (errno == EAGAIN) {
            ret = TRUE;
            break;
         } else {
            io->Status = errno;
            break;
         }
      } else { /* St == 0 */
         break;
      }
   }

   if (io->Buf->len > 0) {
      /* send what we've got so far */
      a_IO_ccc(OpSend, 2, FWD, io->Info, io, NULL);
   }
   if (St == 0) {
      /* TODO: design a general way to avoid reentrancy problems with CCC. */

      /* The following check is necessary because the above CCC operation
       * may abort the whole Chain. */
      if ((io = IO_get(io_key))) {
         /* All data read (EOF) */
         _MSG("IO_read: io->Key=%d io_key=%d\n", io->Key, io_key);
         a_IO_ccc(OpEnd, 2, FWD, io->Info, io, NULL);
      }
   }
   return ret;
}

/*
 * Write data, from a specific buffer, into a file descriptor
 */
static bool_t IO_write(IOData_t *io)
{
   ssize_t St;
   bool_t ret = FALSE;

   _MSG("  IO_write\n");
   io->Status = 0;

   while (1) {
      St = write(io->FD, io->Buf->str, io->Buf->len);
      if (St < 0) {
         /* Error */
         if (errno == EINTR) {
            continue;
         } else if (errno == EAGAIN) {
            ret = TRUE;
            break;
         } else {
            io->Status = errno;
            break;
         }
      } else if (St < io->Buf->len) {
         /* Not all data written */
         dStr_erase (io->Buf, 0, St);
      } else {
         /* All data in buffer written */
         dStr_truncate(io->Buf, 0);
         break;
      }
   }

   return ret;
}

/*
 * Handle background IO for a given FD (reads | writes)
 * (This function gets called when there's activity in the FD)
 */
static int IO_callback(IOData_t *io)
{
   bool_t ret = FALSE;

   _MSG("IO_callback:: (%s) FD = %d\n",
        (io->Op == IORead) ? "IORead" : "IOWrite", io->FD);

   if (io->Op == IORead) {          /* Read */
      ret = IO_read(io);
   } else if (io->Op == IOWrite) {  /* Write */
      ret = IO_write(io);
   }
   return (ret) ? 1 : 0;
}

/*
 * Handle the READ event of a FD.
 */
static void IO_fd_read_cb(int fd, void *data)
{
   int io_key = VOIDP2INT(data);
   IOData_t *io = IO_get(io_key);

   /* There should be no more events on already closed FDs  --Jcid */
   if (io == NULL) {
      MSG_ERR("IO_fd_read_cb: call on already closed io!\n");
      a_IOwatch_remove_fd(fd, DIO_READ);

   } else {
      if (IO_callback(io) == 0)
         a_IOwatch_remove_fd(fd, DIO_READ);
   }
}

/*
 * Handle the WRITE event of a FD.
 */
static void IO_fd_write_cb(int fd, void *data)
{
   int io_key = VOIDP2INT(data);
   IOData_t *io = IO_get(io_key);

   if (io == NULL) {
      /* There must be no more events on already closed FDs  --Jcid */
      MSG_ERR("IO_fd_write_cb: call on already closed io!\n");
      a_IOwatch_remove_fd(fd, DIO_WRITE);

   } else {
      if (IO_callback(io) == 0)
         a_IOwatch_remove_fd(fd, DIO_WRITE);
   }
}

/*
 * Receive an IO request (IORead | IOWrite),
 * Set a watch for it, and let it flow!
 */
static void IO_submit(IOData_t *r_io)
{
   if (r_io->FD < 0) {
      MSG_ERR("IO_submit: FD not initialized\n");
      return;
   }

   /* Insert this IO in ValidIOs */
   IO_ins(r_io);

   _MSG("IO_submit:: (%s) FD = %d\n",
        (r_io->Op == IORead) ? "IORead" : "IOWrite", r_io->FD);

   /* Set FD to background and to close on exec. */
   fcntl(r_io->FD, F_SETFL, O_NONBLOCK | fcntl(r_io->FD, F_GETFL));
   fcntl(r_io->FD, F_SETFD, FD_CLOEXEC | fcntl(r_io->FD, F_GETFD));

   if (r_io->Op == IORead) {
      a_IOwatch_add_fd(r_io->FD, DIO_READ,
                       IO_fd_read_cb, INT2VOIDP(r_io->Key));

   } else if (r_io->Op == IOWrite) {
      a_IOwatch_add_fd(r_io->FD, DIO_WRITE,
                       IO_fd_write_cb, INT2VOIDP(r_io->Key));
   }
}

/*
 * CCC function for the IO module
 * ( Data1 = IOData_t* ; Data2 = NULL )
 */
void a_IO_ccc(int Op, int Branch, int Dir, ChainLink *Info,
              void *Data1, void *Data2)
{
   IOData_t *io;
   DataBuf *dbuf;

   dReturn_if_fail( a_Chain_check("a_IO_ccc", Op, Branch, Dir, Info) );

   if (Branch == 1) {
      if (Dir == BCK) {
         /* Write data using select */
         switch (Op) {
         case OpStart:
            io = IO_new(IOWrite);
            Info->LocalKey = io;
            break;
         case OpSend:
            io = Info->LocalKey;
            if (Data2 && !strcmp(Data2, "FD")) {
               io->FD = *(int*)Data1; /* SockFD */
            } else {
               dbuf = Data1;
               dStr_append_l(io->Buf, dbuf->Buf, dbuf->Size);
               IO_submit(io);
            }
            break;
         case OpEnd:
         case OpAbort:
            io = Info->LocalKey;
            if (io->Buf->len > 0) {
               MSG_WARN("IO_write, closing with pending data not sent\n");
               MSG_WARN(" \"%s\"\n", dStr_printable(io->Buf, 2048));
            }
            /* close FD, remove from ValidIOs and remove its watch */
            IO_close_fd(io, Op == OpEnd ? IO_StopWr : IO_StopRdWr);
            IO_free(io);
            dFree(Info);
            break;
         default:
            MSG_WARN("Unused CCC\n");
            break;
         }
      } else {  /* 1 FWD */
         /* Write-data status */
         switch (Op) {
         default:
            MSG_WARN("Unused CCC\n");
            break;
         }
      }

   } else if (Branch == 2) {
      if (Dir == BCK) {
         /* This part catches the reader's messages */
         switch (Op) {
         case OpStart:
            io = IO_new(IORead);
            Info->LocalKey = io;
            io->Info = Info;
            break;
         case OpSend:
            io = Info->LocalKey;
            if (Data2 && !strcmp(Data2, "FD")) {
               io->FD = *(int*)Data1; /* SockFD */
               IO_submit(io);
            }
            break;
         case OpAbort:
            io = Info->LocalKey;
            IO_close_fd(io, IO_StopRdWr);
            IO_free(io);
            dFree(Info);
            break;
         default:
            MSG_WARN("Unused CCC\n");
            break;
         }
      } else {  /* 2 FWD */
         /* Send read-data */
         io = Data1;
         switch (Op) {
         case OpSend:
            dbuf = a_Chain_dbuf_new(io->Buf->str, io->Buf->len, 0);
            a_Chain_fcb(OpSend, Info, dbuf, NULL);
            dFree(dbuf);
            break;
         case OpEnd:
            a_Chain_fcb(OpEnd, Info, NULL, NULL);
            IO_close_fd(io, IO_StopRdWr); /* IO_StopRd would leak FDs */
            IO_free(io);
            dFree(Info);
            break;
         default:
            MSG_WARN("Unused CCC\n");
            break;
         }
      }
   }
}

