/* POSIX compatible signal blocking.
   Copyright (C) 2008-2023 Free Software Foundation, Inc.
   Written by Eric Blake <ebb9@byu.net>, 2008.

   This file is free software: you can redistribute it and/or modify
   it under the terms of the GNU Lesser General Public License as
   published by the Free Software Foundation; either version 2.1 of the
   License, or (at your option) any later version.

   This file 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 Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public License
   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */

#include <config.h>

/* Specification.  */
#include <signal.h>

#include <errno.h>
#include <stdint.h>
#include <stdlib.h>

/* This implementation of sigaction is tailored to native Windows behavior:
   signal() has SysV semantics (ie. the handler is uninstalled before
   it is invoked).  This is an inherent data race if an asynchronous
   signal is sent twice in a row before we can reinstall our handler,
   but there's nothing we can do about it.  Meanwhile, sigprocmask()
   is not present, and while we can use the gnulib replacement to
   provide critical sections, it too suffers from potential data races
   in the face of an ill-timed asynchronous signal.  And we compound
   the situation by reading static storage in a signal handler, which
   POSIX warns is not generically async-signal-safe.  Oh well.

   Additionally:
     - We don't implement SA_NOCLDSTOP or SA_NOCLDWAIT, because SIGCHLD
       is not defined.
     - We don't implement SA_ONSTACK, because sigaltstack() is not present.
     - We ignore SA_RESTART, because blocking native Windows API calls are
       not interrupted anyway when an asynchronous signal occurs, and the
       MSVCRT runtime never sets errno to EINTR.
     - We don't implement SA_SIGINFO because it is impossible to do so
       portably.

   POSIX states that an application should not mix signal() and
   sigaction().  We support the use of signal() within the gnulib
   sigprocmask() substitute, but all other application code linked
   with this module should stick with only sigaction().  */

/* Check some of our assumptions.  */
#if defined SIGCHLD || defined HAVE_SIGALTSTACK || defined HAVE_SIGINTERRUPT
# error "Revisit the assumptions made in the sigaction module"
#endif

/* Out-of-range substitutes make a good fallback for uncatchable
   signals.  */
#ifndef SIGKILL
# define SIGKILL (-1)
#endif
#ifndef SIGSTOP
# define SIGSTOP (-1)
#endif

/* On native Windows, as of 2008, the signal SIGABRT_COMPAT is an alias
   for the signal SIGABRT.  Only one signal handler is stored for both
   SIGABRT and SIGABRT_COMPAT.  SIGABRT_COMPAT is not a signal of its own.  */
#if defined _WIN32 && ! defined __CYGWIN__
# undef SIGABRT_COMPAT
# define SIGABRT_COMPAT 6
#endif

/* A signal handler.  */
typedef void (*handler_t) (int signal);

/* Set of current actions.  If sa_handler for an entry is NULL, then
   that signal is not currently handled by the sigaction handler.  */
static struct sigaction volatile action_array[NSIG] /* = 0 */;

/* Signal handler that is installed for signals.  */
static void
sigaction_handler (int sig)
{
  handler_t handler;
  sigset_t mask;
  sigset_t oldmask;
  int saved_errno = errno;
  if (sig < 0 || NSIG <= sig || !action_array[sig].sa_handler)
    {
      /* Unexpected situation; be careful to avoid recursive abort.  */
      if (sig == SIGABRT)
        signal (SIGABRT, SIG_DFL);
      abort ();
    }

  /* Reinstall the signal handler when required; otherwise update the
     bookkeeping so that the user's handler may call sigaction and get
     accurate results.  We know the signal isn't currently blocked, or
     we wouldn't be in its handler, therefore we know that we are not
     interrupting a sigaction() call.  There is a race where any
     asynchronous instance of the same signal occurring before we
     reinstall the handler will trigger the default handler; oh
     well.  */
  handler = action_array[sig].sa_handler;
  if ((action_array[sig].sa_flags & SA_RESETHAND) == 0)
    signal (sig, sigaction_handler);
  else
    action_array[sig].sa_handler = NULL;

  /* Block appropriate signals.  */
  mask = action_array[sig].sa_mask;
  if ((action_array[sig].sa_flags & SA_NODEFER) == 0)
    sigaddset (&mask, sig);
  sigprocmask (SIG_BLOCK, &mask, &oldmask);

  /* Invoke the user's handler, then restore prior mask.  */
  errno = saved_errno;
  handler (sig);
  saved_errno = errno;
  sigprocmask (SIG_SETMASK, &oldmask, NULL);
  errno = saved_errno;
}

/* Change and/or query the action that will be taken on delivery of
   signal SIG.  If not NULL, ACT describes the new behavior.  If not
   NULL, OACT is set to the prior behavior.  Return 0 on success, or
   set errno and return -1 on failure.  */
int
sigaction (int sig, const struct sigaction *restrict act,
           struct sigaction *restrict oact)
{
  sigset_t mask;
  sigset_t oldmask;
  int saved_errno;

  if (sig < 0 || NSIG <= sig || sig == SIGKILL || sig == SIGSTOP
      || (act && act->sa_handler == SIG_ERR))
    {
      errno = EINVAL;
      return -1;
    }

#ifdef SIGABRT_COMPAT
  if (sig == SIGABRT_COMPAT)
    sig = SIGABRT;
#endif

  /* POSIX requires sigaction() to be async-signal-safe.  In other
     words, if an asynchronous signal can occur while we are anywhere
     inside this function, the user's handler could then call
     sigaction() recursively and expect consistent results.  We meet
     this rule by using sigprocmask to block all signals before
     modifying any data structure that could be read from a signal
     handler; this works since we know that the gnulib sigprocmask
     replacement does not try to use sigaction() from its handler.  */
  if (!act && !oact)
    return 0;
  sigfillset (&mask);
  sigprocmask (SIG_BLOCK, &mask, &oldmask);
  if (oact)
    {
      if (action_array[sig].sa_handler)
        *oact = action_array[sig];
      else
        {
          /* Safe to change the handler at will here, since all
             signals are currently blocked.  */
          oact->sa_handler = signal (sig, SIG_DFL);
          if (oact->sa_handler == SIG_ERR)
            goto failure;
          signal (sig, oact->sa_handler);
          oact->sa_flags = SA_RESETHAND | SA_NODEFER;
          sigemptyset (&oact->sa_mask);
        }
    }

  if (act)
    {
      /* Safe to install the handler before updating action_array,
         since all signals are currently blocked.  */
      if (act->sa_handler == SIG_DFL || act->sa_handler == SIG_IGN)
        {
          if (signal (sig, act->sa_handler) == SIG_ERR)
            goto failure;
          action_array[sig].sa_handler = NULL;
        }
      else
        {
          if (signal (sig, sigaction_handler) == SIG_ERR)
            goto failure;
          action_array[sig] = *act;
        }
    }
  sigprocmask (SIG_SETMASK, &oldmask, NULL);
  return 0;

 failure:
  saved_errno = errno;
  sigprocmask (SIG_SETMASK, &oldmask, NULL);
  errno = saved_errno;
  return -1;
}
