/*****************************************************************************
 *  sched/group/group_signal.c
 *
 *   Copyright (C) 2013 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 <sched.h>
#include <assert.h>
#include <signal.h>
#include <errno.h>
#include <debug.h>

#include "sched/sched.h"
#include "group/group.h"
#include "signal/signal.h"

#if defined(HAVE_TASK_GROUP) && !defined(CONFIG_DISABLE_SIGNALS)

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

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

#ifdef HAVE_GROUP_MEMBERS
struct group_signal_s
{
  FAR siginfo_t *siginfo; /* Signal to be dispatched */
  FAR struct tcb_s *dtcb; /* Default, valid TCB */
  FAR struct tcb_s *utcb; /* TCB with this signal unblocked */
  FAR struct tcb_s *atcb; /* This TCB was awakened */
  FAR struct tcb_s *ptcb; /* This TCB received the signal */
};
#endif

/*****************************************************************************
 * Private Data
 *****************************************************************************/

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

/*****************************************************************************
 * Name: group_signal_handler
 *
 * Description:
 *   Callback from group_foreachchild that handles one member of the group.
 *
 * Parameters:
 *   pid - The ID of the group member that may be signalled.
 *   arg - A pointer to a struct group_signal_s instance.
 *
 * Return Value:
 *   0 (OK) on success; a negated errno value on failure.
 *
 *****************************************************************************/

#ifdef HAVE_GROUP_MEMBERS
static int group_signal_handler(pid_t pid, FAR void *arg)
{
  FAR struct group_signal_s *info = (FAR struct group_signal_s *)arg;
  FAR struct tcb_s *tcb;
  FAR sigactq_t *sigact;
  int ret;

  DEBUGASSERT(info);

  /* Get the TCB associated with the group member */

  tcb = sched_gettcb(pid);
  DEBUGASSERT(tcb);
  if (tcb)
    {
      /* Set this one as the default if we have not already set the default. */

      if (!info->dtcb)
        {
          info->dtcb = tcb;
        }

      /* Is the thread waiting for this signal (in this case, the signal is
       * probably blocked).
       */

      if (sigismember(&tcb->sigwaitmask, info->siginfo->si_signo) && !info->atcb)
        {
          /* Yes.. This means that the task is suspended, waiting for this
           * signal to occur. Stop looking and use this TCB.  The
           * requirement is this:  If a task group receives a signal and
           * more than one thread is waiting on that signal, then one and
           * only one indeterminate thread out of that waiting group will
           * receive the signal.
           */

          ret = sig_tcbdispatch(tcb, info->siginfo);
          if (ret < 0)
            {
              return ret;
            }

          /* Limit to one thread */

          info->atcb = tcb;
          if (info->ptcb != NULL);
            {
              return 1; /* Terminate the search */
            }
        }

      /* Is this signal unblocked on this thread? */

      if (!sigismember(&tcb->sigprocmask, info->siginfo->si_signo) &&
          !info->ptcb && tcb != info->atcb)
        {
          /* Yes.. remember this TCB if we have not encountered any
           * other threads that have the signal unblocked.
           */

          if (!info->utcb)
            {
              info->utcb = tcb;
            }

          /* Is there also an action associated with the task? */

          sigact = sig_findaction(tcb, info->siginfo->si_signo);
          if (sigact)
            {
              /* Yes.. then use this thread.  The requirement is this:
               * If a task group receives a signal then one and only one
               * indeterminate thread in the task group which is not
               * blocking the signal will receive the signal.
               */

              ret = sig_tcbdispatch(tcb, info->siginfo);
              if (ret < 0)
                {
                  return ret;
                }

              /* Limit to one thread */

              info->ptcb = tcb;
              if (info->atcb != NULL)
                {
                  return 1; /* Terminate the search */
                }
            }
        }
    }

  return 0; /* Keep searching */
}
#endif

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

/*****************************************************************************
 * Name: group_signal
 *
 * Description:
 *   Send a signal to every member of the group.
 *
 * Parameters:
 *   group - The task group that needs to be signalled.
 *
 * Return Value:
 *   0 (OK) on success; a negated errno value on failure.
 *
 * Assumptions:
 *   Called during task termination in a safe context.  No special precautions
 *   are required here.  Because signals can be sent from interrupt handlers,
 *   this function may be called indirectly in the context of an interrupt
 *   handler.
 *
 *****************************************************************************/

int group_signal(FAR struct task_group_s *group, FAR siginfo_t *siginfo)
{
#ifdef HAVE_GROUP_MEMBERS
  struct group_signal_s info;
  FAR struct tcb_s *tcb;
  int ret;

  DEBUGASSERT(group && siginfo);

  info.siginfo = siginfo;
  info.dtcb    = NULL;     /* Default, valid TCB */
  info.utcb    = NULL;     /* TCB with this signal unblocked */
  info.atcb    = NULL;     /* This TCB was awakened */
  info.ptcb    = NULL;     /* This TCB received the signal */

  /* Make sure that pre-emption is disabled to that we signal all of the
   * members of the group before any of them actually run. (This does
   * nothing if were were called from an interrupt handler).
   */

  sched_lock();

  /* Now visit each member of the group and perform signal handling checks. */

  ret = group_foreachchild(group, group_signal_handler, &info);
  if (ret < 0)
    {
      goto errout;
    }

  /* We need to dispatch the signal in any event (if nothing else so that it
   * can be added to the pending signal list). If we found a thread with the
   * signal unblocked, then use that thread.
   */

  if (info.atcb == NULL && info.ptcb == NULL)
    {
      if (info.utcb)
        {
          tcb = info.utcb;
        }

      /* Otherwise use the default TCB.  There should always be a default
       * TCB. It will have the signal blocked, but can be used to get the
       * signal to a pending state.
       */

      else /* if (info.dtcb) */
        {
          DEBUGASSERT(info.dtcb);
          tcb = info.dtcb;
        }

      /* Now deliver the signal to the selected group member */

      ret = sig_tcbdispatch(tcb, siginfo);
    }

errout:
  sched_unlock();
  return ret;

#else

  return -ENOSYS;

#endif
}

#endif /* HAVE_TASK_GROUP && !CONFIG_DISABLE_SIGNALS */
