/****************************************************************************
 * libc/aio/lio_listio.c
 *
 *   Copyright (C) 2014 Gregory Nutt. All rights reserved.
 *   Author: Gregory Nutt <gnutt@nuttx.org>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 * 3. Neither the name NuttX nor the names of its contributors may be
 *    used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <nuttx/config.h>

#include <unistd.h>
#include <signal.h>
#include <aio.h>
#include <assert.h>
#include <errno.h>

#include "lib_internal.h"
#include "aio/aio.h"

#ifdef CONFIG_FS_AIO

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/
/* Configuration ************************************************************/

/****************************************************************************
 * Private Types
 ****************************************************************************/

struct lio_sighand_s
{
  FAR struct aiocb * const *list;  /* List of I/O operations */
  FAR struct sigevent *sig;        /* Describes how to signal the caller */
  int nent;                        /* Number or elements in list[] */
  pid_t pid;                       /* ID of client */
  sigset_t oprocmask;              /* sigprocmask to restore */
  struct sigaction oact;           /* Signal handler to restore */
};

/****************************************************************************
 * Private Variables
 ****************************************************************************/

/****************************************************************************
 * Public Variables
 ****************************************************************************/

/****************************************************************************
 * Private Functions
 ****************************************************************************/

/****************************************************************************
 * Name: lio_checkio
 *
 * Description:
 *  Check if all I/O operations in the list are complete.
 *
 * Input Parameters:
 *   list - The list of I/O operations to be performed
 *   nent - The number of elements in the list
 *
 * Returned Value:
 *  Zero (OK) is returned if all I/O completed successfully.
 *  -EINPROGRESS is returned if one or more I/Os have not yet completed.
 *  The negated errno value if first error noted in the case where all I/O
 *  completed but one or more I/Os completed with an error.
 *
 * Assumptions:
 *  The scheduler is locked and no I/O can complete asynchronously with
 *  the logic in this function.
 *
 ****************************************************************************/

static int lio_checkio(FAR struct aiocb * const *list, int nent)
{
  FAR struct aiocb *aiocbp;
  int ret;
  int i;

  ret = OK; /* Assume success */

  /* Check each entry in the list.  Break out of the loop if any entry
   * has not completed.
   */

  for (i = 0; i < nent; i++)
    {
      /* Skip over NULL entries */

      aiocbp = list[i];
      if (aiocbp)
        {
          /* Check if the I/O has completed */

          if (aiocbp->aio_result == -EINPROGRESS)
            {
              /* No.. return -EINPROGRESS */

              return -EINPROGRESS;
            }

          /* Check for an I/O error */

          else if (aiocbp->aio_result < 0 && ret == OK)
            {
              /* Some other error other than -EINPROGRESS */

              ret = aiocbp->aio_result;
            }
        }
    }

  /* All of the I/Os have completed */

  return ret;
}

/****************************************************************************
 * Name: lio_sighandler
 *
 * Description:
 *   Handle the SIGPOLL signal.
 *
 * Input Parameters:
 *   signo   - The number of the signal that we caught (SIGPOLL)
 *   info    - Information accompanying the signal
 *   context - Not used in NuttX
 *
 * Returned Value:
 *  None
 *
 ****************************************************************************/

static void lio_sighandler(int signo, siginfo_t *info, void *ucontext)
{
  FAR struct aiocb *aiocbp;
  FAR struct lio_sighand_s *sighand;
  int ret;

  DEBUGASSERT(signo == SIGPOLL && info);

  /* The info structure should contain a pointer to the AIO control block */

  aiocbp = (FAR struct aiocb *)info->si_value.sival_ptr;
  DEBUGASSERT(aiocbp && aiocbp->aio_result != -EINPROGRESS);

  /* Recover our private data from the AIO control block */

  sighand = (FAR struct lio_sighand_s *)aiocbp->aio_priv;
  DEBUGASSERT(sighand && sighand->list);
  aiocbp->aio_priv = NULL;

  /* Prevent any asynchronous I/O completions while the signal handler runs */

  sched_lock();

  /* Check if all of the pending I/O has completed */

  ret = lio_checkio(sighand->list, sighand->nent);
  if (ret != -EINPROGRESS)
    {
      /* All pending I/O has completed */
      /* Restore the signal handler */

      (void)sigaction(SIGPOLL, &sighand->oact, NULL);

      /* Restore the sigprocmask */

      (void)sigprocmask(SIG_SETMASK, &sighand->oprocmask, NULL);

      /* Signal the client */

      if (sighand->sig->sigev_notify == SIGEV_SIGNAL)
        {
#ifdef CONFIG_CAN_PASS_STRUCTS
          (void)sigqueue(sighand->pid, sighand->sig->sigev_signo,
                         sighand->sig->sigev_value);
#else
          (void)sigqueue(sighand->pid, sighand->sig->sigev_signo,
                         sighand->sig->sigev_value.sival_ptr);
#endif
        }

      /* And free the container */

      lib_free(sighand);
    }

  sched_unlock();
}

/****************************************************************************
 * Name: lio_sigsetup
 *
 * Description:
 *   Setup a signal handler to detect when until all I/O completes.
 *
 * Input Parameters:
 *   list - The list of I/O operations to be performed
 *   nent - The number of elements in the list
 *
 * Returned Value:
 *  Zero (OK) is returned if all I/O completed successfully; Otherwise, a
 *  negated errno value is returned corresponding to the first error
 *  detected.
 *
 * Assumptions:
 *  The scheduler is locked and no I/O can complete asynchronously with
 *  the logic in this function.
 *
 ****************************************************************************/

static int lio_sigsetup(FAR struct aiocb * const *list, int nent,
                        FAR struct sigevent *sig)
{
  FAR struct aiocb *aiocbp;
  FAR struct lio_sighand_s *sighand;
  sigset_t sigset;
  struct sigaction act;
  int status;
  int i;

  /* Allocate a structure to pass data to the signal handler */

  sighand = (FAR struct lio_sighand_s *)lib_zalloc(sizeof(struct lio_sighand_s));
  if (!sighand)
    {
      fdbg("ERROR: lib_zalloc failed\n");
      return -ENOMEM;
    }

  /* Initialize the allocated structure */

  sighand->list = list;
  sighand->sig  = sig;
  sighand->nent = nent;
  sighand->pid  = getpid();

  /* Save this structure as the private data attached to each aiocb */

  for (i = 0; i < nent; i++)
    {
      /* Skip over NULL entries in the list */

      aiocbp = list[i];
      if (aiocbp)
        {
          FAR void *priv = NULL;

          /* Check if I/O is pending for  this entry */

          if (aiocbp->aio_result == -EINPROGRESS)
            {
              priv = (FAR void *)sighand;
            }

          aiocbp->aio_priv = priv;
        }
    }

  /* Make sure that SIGPOLL is not blocked */

  (void)sigemptyset(&sigset);
  (void)sigaddset(&sigset, SIGPOLL);
  status = sigprocmask(SIG_UNBLOCK, &sigset, &sighand->oprocmask);
  if (status != OK)
    {
      int errcode = get_errno();
      fdbg("ERROR sigprocmask failed: %d\n", errcode);
      DEBUGASSERT(errcode > 0);
      return -errcode;
    }

  /* Attach our signal handler */

  printf("waiter_main: Registering signal handler\n" );
  act.sa_sigaction = lio_sighandler;
  act.sa_flags = SA_SIGINFO;

  (void)sigfillset(&act.sa_mask);
  (void)sigdelset(&act.sa_mask, SIGPOLL);

  status = sigaction(SIGPOLL, &act, &sighand->oact);
  if (status != OK)
    {
      int errcode = get_errno();
      fdbg("ERROR sigaction failed: %d\n", errcode);
      DEBUGASSERT(errcode > 0);
      return -errcode;
    }

  return OK;
}

/****************************************************************************
 * Name: lio_waitall
 *
 * Description:
 *  Wait for all I/O operations in the list to be complete.
 *
 * Input Parameters:
 *   list - The list of I/O operations to be performed
 *   nent - The number of elements in the list
 *
 * Returned Value:
 *  Zero (OK) is returned if all I/O completed successfully; Otherwise, a
 *  negated errno value is returned corresponding to the first error
 *  detected.
 *
 * Assumptions:
 *  The scheduler is locked and no I/O can complete asynchronously with
 *  the logic in this function.
 *
 ****************************************************************************/

static int lio_waitall(FAR struct aiocb * const *list, int nent)
{
  sigset_t set;
  int ret;

  /* Loop until all I/O completes */

  for (;;)
    {
      /* Check if all I/O has completed */

      ret = lio_checkio(list, nent);
      if (ret != -EINPROGRESS)
        {
          /* All I/O has completed.. We are finished.  */

          return ret;
        }

      /* Then wait for SIGPOLL -- indefinitely.
       *
       * NOTE: If completion of the I/O causes other signals to be generated
       * first, then this will wake up and return EINTR instead of success.
       */

      sigemptyset(&set);
      sigaddset(&set, SIGPOLL);

      ret = sigwaitinfo(&set, NULL);
      if (ret < 0)
        {
          /* The most likely reason that we would get here is because some
           * unrelated signal has been received.
           */

          int errcode = get_errno();
          fdbg("ERROR: sigwaitinfo failed: %d\n", errcode);
          DEBUGASSERT(errcode > 0);
          return -errcode;
        }
    }
}

/****************************************************************************
 * Public Functions
 ****************************************************************************/

/****************************************************************************
 * Name: lio_listio
 *
 * Description:
 *   The lio_listio() function initiates a list of I/O requests with a
 *   single function call.
 *
 *   The 'mode' argument takes one of the values LIO_WAIT or LIO_NOWAIT
 *   declared in <aio.h> and determines whether the function returns when
 *   the I/O operations have been completed, or as soon as the operations
 *   have been queued. If the 'mode' argument is LIO_WAIT, the function will
 *   wait until all I/O is complete and the 'sig' argument will be ignored.
 *
 *   If the 'mode' argument is LIO_NOWAIT, the function will return
 *   immediately, and asynchronous notification will occur, according to the
 *   'sig' argument, when all the I/O operations complete. If 'sig' is NULL,
 *   then no asynchronous notification will occur. If 'sig' is not NULL,
 *   asynchronous notification occurs when all the requests in 'list' have
 *   completed.
 *
 *   The I/O requests enumerated by 'list' are submitted in an unspecified
 *   order.
 *
 *   The 'list' argument is an array of pointers to aiocb structures. The
 *   array contains 'nent 'elements. The array may contain NULL elements,
 *   which will be ignored.
 *
 *   If the buffer pointed to by 'list' or the aiocb structures pointed to
 *   by the elements of the array 'list' become illegal addresses before all
 *   asynchronous I/O completed and, if necessary, the notification is
 *   sent, then the behavior is undefined. If the buffers pointed to by the
 *   aio_buf member of the aiocb structure pointed to by the elements of
 *   the array 'list' become illegal addresses prior to the asynchronous
 *   I/O associated with that aiocb structure being completed, the behavior
 *   is undefined.
 *
 *   The aio_lio_opcode field of each aiocb structure specifies the
 *   operation to be performed. The supported operations are LIO_READ,
 *   LIO_WRITE, and LIO_NOP; these symbols are defined in <aio.h>. The
 *   LIO_NOP operation causes the list entry to be ignored. If the
 *   aio_lio_opcode element is equal to LIO_READ, then an I/O operation is
 *   submitted as if by a call to aio_read() with the aiocbp equal to the
 *   address of the aiocb structure. If the aio_lio_opcode element is equal
 *   to LIO_WRITE, then an I/O operation is submitted as if by a call to
 *   aio_write() with the aiocbp equal to the address of the aiocb
 *   structure.
 *
 *   The aio_fildes member specifies the file descriptor on which the
 *   operation is to be performed.
 *
 *   The aio_buf member specifies the address of the buffer to or from which
 *   the data is transferred.
 *
 *   The aio_nbytes member specifies the number of bytes of data to be
 *   transferred.
 *
 *   The members of the aiocb structure further describe the I/O operation
 *   to be performed, in a manner identical to that of the corresponding
 *   aiocb structure when used by the aio_read() and aio_write() functions.
 *
 *   The 'nent' argument specifies how many elements are members of the list;
 *   that is, the length of the array.
 *
 * Input Parameters:
 *   mode - Either LIO_WAIT or LIO_NOWAIT
 *   list - The list of I/O operations to be performed
 *   nent - The number of elements in the list
 *   sig  - Used to notify the caller when the I/O is performed
 *          asynchronously.
 *
 * Returned Value:
 *   If the mode argument has the value LIO_NOWAIT, the lio_listio()
 *   function will return the value zero if the I/O operations are
 *   successfully queued; otherwise, the function will return the value
 *   -1 and set errno to indicate the error.
 *
 *   If the mode argument has the value LIO_WAIT, the lio_listio() function
 *   will return the value zero when all the indicated I/O has completed
 *   successfully. Otherwise, lio_listio() will return a value of -1 and
 *   set errno to indicate the error.
 *
 *   In either case, the return value only indicates the success or failure
 *   of the lio_listio() call itself, not the status of the individual I/O
 *   requests. In some cases one or more of the I/O requests contained in
 *   the list may fail. Failure of an individual request does not prevent
 *   completion of any other individual request. To determine the outcome
 *   of each I/O request, the application must examine the error status
 *   associated with each aiocb control block. The error statuses so
 *   returned are identical to those returned as the result of an aio_read()
 *   or aio_write() function.
 *
 *   The lio_listio() function will fail if:
 *
 *     EAGAIN - The resources necessary to queue all the I/O requests were
 *       not available. The application may check the error status for each
 *       aiocb to determine the individual request(s) that failed.
 *     EAGAIN - The number of entries indicated by 'nent' would cause the
 *       system-wide limit {AIO_MAX} to be exceeded.
 *     EINVAL - The mode argument is not a proper value, or the value of
 *       'nent' was greater than {AIO_LISTIO_MAX}.
 *     EINTR - A signal was delivered while waiting for all I/O requests to
 *       complete during an LIO_WAIT operation. Note that, since each I/O
 *       operation invoked by lio_listio() may possibly provoke a signal when
 *       it completes, this error return may be caused by the completion of
 *       one (or more) of the very I/O operations being awaited. Outstanding
 *       I/O requests are not cancelled, and the application will examine
 *       each list element to determine whether the request was initiated,
 *       cancelled, or completed.
 *     EIO - One or more of the individual I/O operations failed. The
 *       application may check the error status for each aiocb structure to
 *       determine the individual request(s) that failed.
 *
 *   In addition to the errors returned by the lio_listio() function, if the
 *   lio_listio() function succeeds or fails with errors of EAGAIN, EINTR, or
 *   EIO, then some of the I/O specified by the list may have been initiated.
 *   If the lio_listio() function fails with an error code other than EAGAIN,
 *   EINTR, or EIO, no operations from the list will have been initiated. The
 *   I/O operation indicated by each list element can encounter errors specific
 *   to the individual read or write function being performed. In this event,
 *   the error status for each aiocb control block contains the associated
 *   error code. The error codes that can be set are the same as would be
 *   set by a read() or write() function, with the following additional
 *   error codes possible:
 *
 *     EAGAIN - The requested I/O operation was not queued due to resource
 *       limitations.
 *     ECANCELED - The requested I/O was cancelled before the I/O completed
 *       due to an explicit aio_cancel() request.
 *     EFBIG - The aiocbp->aio_lio_opcode is LIO_WRITE, the file is a
 *       regular file, aiocbp->aio_nbytes is greater than 0, and the
 *       aiocbp->aio_offset is greater than or equal to the offset maximum
 *       in the open file description associated with aiocbp->aio_fildes.
 *     EINPROGRESS - The requested I/O is in progress.
 *     EOVERFLOW - The aiocbp->aio_lio_opcode is LIO_READ, the file is a
 *       regular file, aiocbp->aio_nbytes is greater than 0, and the
 *       aiocbp->aio_offset is before the end-of-file and is greater than
 *       or equal to the offset maximum in the open file description
 *       associated with aiocbp->aio_fildes.
 *
 ****************************************************************************/

int lio_listio(int mode, FAR struct aiocb *const list[], int nent,
               FAR struct sigevent *sig)
{
  FAR struct aiocb *aiocbp;
  int nqueued;
  int errcode;
  int retcode;
  int status;
  int ret;
  int i;

  DEBUGASSERT(mode == LIO_WAIT || mode == LIO_NOWAIT);
  DEBUGASSERT(list);

  nqueued = 0;    /* No I/O operations yet queued */
  ret     = OK;   /* Assume success */

  /* Lock the scheduler so that no I/O events can complete on the worker
   * thread until we set our wait set up.  Pre-emption will, of course, be
   * re-enabled while we are waiting for the signal.
   */

  sched_lock();

  /* Submit each asynchronous I/O operation in the list, skipping over NULL
   * entries.
   */

  for (i = 0; i < nent; i++)
    {
      /* Skip over NULL entries */

      aiocbp = list[i];
      if (aiocbp)
        {
          /* Submit the operation according to its opcode */

          status = OK;
          switch (aiocbp->aio_lio_opcode)
            {
            case LIO_NOP:
              {
                /* Mark the do-nothing operation complete */

                aiocbp->aio_result = OK;
              }
              break;

            case LIO_READ:
            case LIO_WRITE:
              {
                if (aiocbp->aio_lio_opcode == LIO_READ)
                  {
                    /* Submit the asynchronous read operation */

                    status = aio_read(aiocbp);
                  }
                else
                  {
                    /* Submit the asynchronous write operation */

                    status = aio_write(aiocbp);
                  }

                if (status < 0)
                  {
                    /* Failed to queue the I/O.  Set up the error return. */

                    errcode = get_errno();
                    fdbg("ERROR: aio_read/write failed: %d\n", errcode);
                    DEBUGASSERT(errcode > 0);
                    aiocbp->aio_result = -errcode;
                    ret = ERROR;
                  }
                else
                  {
                    /* Increment the count of successfully queue operations */

                    nqueued++;
                  }
              }
              break;

            default:
              {
                /* Make the invalid operation complete with an error */

                fdbg("ERROR: Unrecognized opcode: %d\n", aiocbp->aio_lio_opcode);
                aiocbp->aio_result = -EINVAL;
                ret = ERROR;
              }
              break;
            }
        }
    }

  /* If there was any failure in queuing the I/O, EIO will be returned */

  retcode = EIO;

  /* Now what? Three possibilities:
   *
   * Case 1: mode == LIO_WAIT
   *
   *   Ignore the sig argument; Do no return until all I/O completes.
   */

  if (mode == LIO_WAIT)
    {
      /* Don't wait if all if no I/O was queue */

      if (nqueued > 0)
        {
          /* Wait until all I/O completes.  The scheduler will be unlocked
           * while we are waiting.
           */

          status = lio_waitall(list, nent);
          if (status < 0 && ret != OK)
            {
              /* Something bad happened while waiting and this is the first
               * error to be reported.
               */

              retcode = -status;
              ret     = ERROR;
            }
        }
    }

  /* Case 2: mode == LIO_NOWAIT and sig != NULL
   *
   *   If any I/O was queued, then setup to signal the caller when all of
   *   the transfers complete.
   *
   *   If no I/O was queue, then we I suppose that we need to signal the
   *   caller ourself?
   */

  else if (sig && sig->sigev_notify == SIGEV_SIGNAL)
    {
      if (nqueued > 0)
        {
          /* Setup a signal handler to detect when until all I/O completes. */

          status = lio_sigsetup(list, nent, sig);
          if (status < 0 && ret != OK)
            {
              /* Something bad happened while setting up the signal and this
               * is the first error to be reported.
               */

              retcode = -status;
              ret     = ERROR;
            }
        }
      else
        {
#ifdef CONFIG_CAN_PASS_STRUCTS
          status = sigqueue(getpid(), sig->sigev_signo,
                            sig->sigev_value);
#else
          status = sigqueue(getpid(), sig->sigev_signo,
                            sig->sigev_value.sival_ptr);
#endif

          if (status < 0 && ret != OK)
            {
              /* Something bad happened while signalling ourself and this is
               * the first error to be reported.
               */

              retcode = get_errno();
              ret     = ERROR;
            }
        }
    }

  /* Case 3: mode == LIO_NOWAIT and sig == NULL
   *
   *   Just return now.
   */

  sched_unlock();
  if (ret < 0)
    {
      set_errno(retcode);
      return ERROR;
    }

  return OK;
}

#endif /* CONFIG_FS_AIO */
