text: /****************************************************************************
   * libs/libc/aio/lio_listio.c
   *
   *   Copyright (C) 2014-2015, 2018 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 <nuttx/signal.h>
  
  #include "libc.h"
  #include "aio/aio.h"
  
  #ifdef CONFIG_FS_AIO
  
  /****************************************************************************
   * 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 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 */
  
        DEBUGVERIFY(nxsig_notification(sighand->pid, &sighand->sig,
                                       SI_ASYNCIO));
  
        /* 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 set;
    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)
      {
        ferr("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(&set);
    (void)sigaddset(&set, SIGPOLL);
    status = sigprocmask(SIG_UNBLOCK, &set, &sighand->oprocmask);
    if (status != OK)
      {
        int errcode = get_errno();
        ferr("ERROR sigprocmask failed: %d\n", errcode);
        DEBUGASSERT(errcode > 0);
        return -errcode;
      }
  
    /* Attach our signal handler */
  
    finfo("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();
  
        ferr("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)
          {
            int errcode = get_errno();
  
            /* The most likely reason that we would get here is because some
             * unrelated signal has been received.
             */
  
            ferr("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();
                      ferr("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 */
  
                  ferr("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 != NULL)
      {
        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
          {
            status = nxsig_notification(getpid(), sig, SI_ASYNCIO);
            if (status < 0 && ret == OK)
              {
                /* Something bad happened while performing the notification
                 * and this is the first error to be reported.
                 */
  
                 retcode = -status;
                 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 */
  