/* Tests for pthread_attr_setsigmask_np, pthread_attr_getsigmask_np.
   Copyright (C) 2020 Free Software Foundation, Inc.
   This file is part of the GNU C Library.

   The GNU C Library 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.

   The GNU C Library 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 the GNU C Library; if not, see
   <https://www.gnu.org/licenses/>.  */

/* This thread uses different masked status for SIGUSR1, SIGUSR2,
   SIGHUP to determine if signal masks are applied to new threads as
   expected.  */

#include <signal.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <support/check.h>
#include <support/xsignal.h>
#include <support/xthread.h>
#include <threads.h>

typedef bool signals[_NSIG];

static const char *
masked_or_unmasked (bool masked)
{
  if (masked)
    return "masked";
  else
    return "unmasked";
}

/* Report an error if ACTUAL_MASK does not match EXPECTED_MASK.
   CONTEXT is used in error messages.  */
static void
check_sigmask (const char *context, signals expected_mask,
               const sigset_t *actual_mask)
{
  for (int sig = 1; sig < _NSIG; ++sig)
    if (sigismember (actual_mask, sig) != expected_mask[sig])
      {
        support_record_failure ();
        printf ("error: %s: signal %d should be %s, but is %s\n",
                context, sig,
                masked_or_unmasked (sigismember (actual_mask, sig)),
                masked_or_unmasked (expected_mask[sig]));
      }
}

/* Report an error if the current thread signal mask does not match
   EXPECTED_MASK.  CONTEXT is used in error messages.  */
static void
check_current_sigmask (const char *context, signals expected_mask)
{
  sigset_t actual_mask;
  xpthread_sigmask (SIG_SETMASK, NULL, &actual_mask);
  check_sigmask (context, expected_mask, &actual_mask);
}

/* Thread start routine which checks the current thread signal mask
   against CLOSURE.  */
static void *
check_sigmask_thread_function (void *closure)
{
  check_current_sigmask ("on thread", closure);
  return NULL;
}

/* Same for C11 threads.  */
static int
check_sigmask_thread_function_c11 (void *closure)
{
  check_current_sigmask ("on C11 thread", closure);
  return 0;
}

/* Launch a POSIX thread with ATTR (which can be NULL) and check that
   it has the expected signal mask.  */
static void
check_posix_thread (pthread_attr_t *attr, signals expected_mask)
{
  xpthread_join (xpthread_create (attr, check_sigmask_thread_function,
                                  expected_mask));
}

/* Launch a C11 thread and check that it has the expected signal
   mask.  */
static void
check_c11_thread (signals expected_mask)
{
  thrd_t thr;
  TEST_VERIFY_EXIT (thrd_create (&thr, check_sigmask_thread_function_c11,
                                 expected_mask) == thrd_success);
  TEST_VERIFY_EXIT (thrd_join (thr, NULL) == thrd_success);
}

static int
do_test (void)
{
  check_current_sigmask ("initial mask", (signals) { false, });
  check_posix_thread (NULL, (signals) { false, });
  check_c11_thread ((signals) { false, });

  sigset_t set;
  sigemptyset (&set);
  sigaddset (&set, SIGUSR1);
  xpthread_sigmask (SIG_SETMASK, &set, NULL);
  check_current_sigmask ("SIGUSR1 masked", (signals) { [SIGUSR1] = true, });
  /* The signal mask is inherited by the new thread.  */
  check_posix_thread (NULL, (signals) { [SIGUSR1] = true, });
  check_c11_thread ((signals) { [SIGUSR1] = true, });

  pthread_attr_t attr;
  xpthread_attr_init (&attr);
  TEST_COMPARE (pthread_attr_getsigmask_np (&attr, &set),
                PTHREAD_ATTR_NO_SIGMASK_NP);
  /* By default, the signal mask is inherited (even with an explicit
     thread attribute).  */
  check_posix_thread (&attr, (signals) { [SIGUSR1] = true, });

  /* Check that pthread_attr_getsigmask_np can obtain the signal
     mask.  */
  sigemptyset (&set);
  sigaddset (&set, SIGUSR2);
  TEST_COMPARE (pthread_attr_setsigmask_np (&attr, &set), 0);
  sigemptyset (&set);
  TEST_COMPARE (pthread_attr_getsigmask_np (&attr, &set), 0);
  check_sigmask ("pthread_attr_getsigmask_np", (signals) { [SIGUSR2] = true, },
                 &set);

  /* Check that a thread is launched with the configured signal
     mask.  */
  check_current_sigmask ("SIGUSR1 masked", (signals) { [SIGUSR1] = true, });
  check_posix_thread (&attr, (signals) { [SIGUSR2] = true, });
  check_current_sigmask ("SIGUSR1 masked", (signals) { [SIGUSR1] = true, });

  /* But C11 threads remain at inheritance.  */
  check_c11_thread ((signals) { [SIGUSR1] = true, });

  /* Check that filling the original signal set does not affect thread
     creation.  */
  sigfillset (&set);
  check_posix_thread (&attr, (signals) { [SIGUSR2] = true, });

  /* Check that clearing the signal in the attribute restores
     inheritance.  */
  TEST_COMPARE (pthread_attr_setsigmask_np (&attr, NULL), 0);
  TEST_COMPARE (pthread_attr_getsigmask_np (&attr, &set),
                PTHREAD_ATTR_NO_SIGMASK_NP);
  check_posix_thread (&attr, (signals) { [SIGUSR1] = true, });

  /* Mask SIGHUP via the default thread attribute.  */
  sigemptyset (&set);
  sigaddset (&set, SIGHUP);
  TEST_COMPARE (pthread_attr_setsigmask_np (&attr, &set), 0);
  TEST_COMPARE (pthread_setattr_default_np (&attr), 0);

  /* Check that the mask was applied to the default attribute.  */
  xpthread_attr_destroy (&attr);
  TEST_COMPARE (pthread_getattr_default_np (&attr), 0);
  sigaddset (&set, SIGHUP);
  TEST_COMPARE (pthread_attr_getsigmask_np (&attr, &set), 0);
  check_sigmask ("default attribute", (signals) { [SIGHUP] = true, }, &set);
  xpthread_attr_destroy (&attr);

  /* Check that the default attribute is applied.  */
  check_posix_thread (NULL, (signals) { [SIGHUP] = true, });
  check_c11_thread ((signals) { [SIGHUP] = true, });

  /* An explicit attribute with no signal mask triggers inheritance
     even if the default has been changed.  */
  xpthread_attr_init (&attr);
  check_posix_thread (&attr, (signals) { [SIGUSR1] = true, });

  /* Explicitly setting the signal mask affects the new thread even
     with a default attribute.  */
  sigemptyset (&set);
  sigaddset (&set, SIGUSR2);
  TEST_COMPARE (pthread_attr_setsigmask_np (&attr, &set), 0);
  check_posix_thread (&attr, (signals) { [SIGUSR2] = true, });

  /* Resetting the default attribute brings back the old inheritance
     behavior.  */
  xpthread_attr_destroy (&attr);
  xpthread_attr_init (&attr);
  TEST_COMPARE (pthread_setattr_default_np (&attr), 0);
  xpthread_attr_destroy (&attr);
  check_posix_thread (NULL, (signals) { [SIGUSR1] = true, });
  check_c11_thread ((signals) { [SIGUSR1] = true, });

  return 0;
}

#include <support/test-driver.c>