strtoumax: fix typo in previous commit.
[gnulib.git] / lib / sigprocmask.c
index 9649f8b..8de3777 100644 (file)
@@ -1,11 +1,11 @@
 /* POSIX compatible signal blocking.
-   Copyright (C) 2006-2007 Free Software Foundation, Inc.
+   Copyright (C) 2006-2013 Free Software Foundation, Inc.
    Written by Bruno Haible <bruno@clisp.org>, 2006.
 
-   This program is free software; you can redistribute it and/or modify
+   This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
-   the Free Software Foundation; either version 2, or (at your option)
-   any later version.
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
 
    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
@@ -13,8 +13,7 @@
    GNU General Public License for more details.
 
    You should have received a copy of the GNU General Public License
-   along with this program; if not, write to the Free Software Foundation,
-   Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
 
 #include <config.h>
 
 #include <stdint.h>
 #include <stdlib.h>
 
-/* We assume that a platform without POSIX signal blocking functions also
-   does not have the POSIX sigaction() function, only the signal() function.
-   This is true for Woe32 platforms.  */
+#if HAVE_MSVC_INVALID_PARAMETER_HANDLER
+# include "msvc-inval.h"
+#endif
 
-/* A signal handler.  */
-typedef void (*handler_t) (int signal);
+/* We assume that a platform without POSIX signal blocking functions
+   also does not have the POSIX sigaction() function, only the
+   signal() function.  We also assume signal() has SysV semantics,
+   where any handler is uninstalled prior to being invoked.  This is
+   true for native Windows platforms.  */
+
+/* We use raw signal(), but also provide a wrapper rpl_signal() so
+   that applications can query or change a blocked signal.  */
+#undef signal
+
+/* Provide invalid signal numbers as fallbacks if the uncatchable
+   signals are not defined.  */
+#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 __WIN32__) && ! defined __CYGWIN__
+# undef SIGABRT_COMPAT
+# define SIGABRT_COMPAT 6
+#endif
+#ifdef SIGABRT_COMPAT
+# define SIGABRT_COMPAT_MASK (1U << SIGABRT_COMPAT)
+#else
+# define SIGABRT_COMPAT_MASK 0
+#endif
+
+typedef void (*handler_t) (int);
+
+#if HAVE_MSVC_INVALID_PARAMETER_HANDLER
+static handler_t
+signal_nothrow (int sig, handler_t handler)
+{
+  handler_t result;
+
+  TRY_MSVC_INVAL
+    {
+      result = signal (sig, handler);
+    }
+  CATCH_MSVC_INVAL
+    {
+      result = SIG_ERR;
+      errno = EINVAL;
+    }
+  DONE_MSVC_INVAL;
+
+  return result;
+}
+# define signal signal_nothrow
+#endif
+
+/* Handling of gnulib defined signals.  */
+
+#if GNULIB_defined_SIGPIPE
+static handler_t SIGPIPE_handler = SIG_DFL;
+#endif
+
+#if GNULIB_defined_SIGPIPE
+static handler_t
+ext_signal (int sig, handler_t handler)
+{
+  switch (sig)
+    {
+    case SIGPIPE:
+      {
+        handler_t old_handler = SIGPIPE_handler;
+        SIGPIPE_handler = handler;
+        return old_handler;
+      }
+    default: /* System defined signal */
+      return signal (sig, handler);
+    }
+}
+# undef signal
+# define signal ext_signal
+#endif
 
 int
 sigismember (const sigset_t *set, int sig)
 {
   if (sig >= 0 && sig < NSIG)
-    return (*set >> sig) & 1;
+    {
+      #ifdef SIGABRT_COMPAT
+      if (sig == SIGABRT_COMPAT)
+        sig = SIGABRT;
+      #endif
+
+      return (*set >> sig) & 1;
+    }
   else
     return 0;
 }
@@ -53,6 +138,11 @@ sigaddset (sigset_t *set, int sig)
 {
   if (sig >= 0 && sig < NSIG)
     {
+      #ifdef SIGABRT_COMPAT
+      if (sig == SIGABRT_COMPAT)
+        sig = SIGABRT;
+      #endif
+
       *set |= 1U << sig;
       return 0;
     }
@@ -68,6 +158,11 @@ sigdelset (sigset_t *set, int sig)
 {
   if (sig >= 0 && sig < NSIG)
     {
+      #ifdef SIGABRT_COMPAT
+      if (sig == SIGABRT_COMPAT)
+        sig = SIGABRT;
+      #endif
+
       *set &= ~(1U << sig);
       return 0;
     }
@@ -78,15 +173,16 @@ sigdelset (sigset_t *set, int sig)
     }
 }
 
+
 int
 sigfillset (sigset_t *set)
 {
-  *set = (2U << (NSIG - 1)) - 1;
+  *set = ((2U << (NSIG - 1)) - 1) & ~ SIGABRT_COMPAT_MASK;
   return 0;
 }
 
 /* Set of currently blocked signals.  */
-static sigset_t blocked_set /* = 0 */;
+static volatile sigset_t blocked_set /* = 0 */;
 
 /* Set of currently blocked and pending signals.  */
 static volatile sig_atomic_t pending_array[NSIG] /* = { 0 } */;
@@ -95,6 +191,12 @@ static volatile sig_atomic_t pending_array[NSIG] /* = { 0 } */;
 static void
 blocked_handler (int sig)
 {
+  /* Reinstall the handler, in case the signal occurs multiple times
+     while blocked.  There is an inherent race where an asynchronous
+     signal in between when the kernel uninstalled the handler and
+     when we reinstall it will trigger the default handler; oh
+     well.  */
+  signal (sig, blocked_handler);
   if (sig >= 0 && sig < NSIG)
     pending_array[sig] = 1;
 }
@@ -108,12 +210,13 @@ sigpending (sigset_t *set)
   for (sig = 0; sig < NSIG; sig++)
     if (pending_array[sig])
       pending |= 1U << sig;
-  return pending;
+  *set = pending;
+  return 0;
 }
 
 /* The previous signal handlers.
    Only the array elements corresponding to blocked signals are relevant.  */
-static handler_t old_handlers[NSIG];
+static volatile handler_t old_handlers[NSIG];
 
 int
 sigprocmask (int operation, const sigset_t *set, sigset_t *old_set)
@@ -128,65 +231,119 @@ sigprocmask (int operation, const sigset_t *set, sigset_t *old_set)
       sigset_t to_block;
 
       switch (operation)
-       {
-       case SIG_BLOCK:
-         new_blocked_set = blocked_set | *set;
-         break;
-       case SIG_SETMASK:
-         new_blocked_set = *set;
-         break;
-       case SIG_UNBLOCK:
-         new_blocked_set = blocked_set & ~*set;
-         break;
-       default:
-         errno = EINVAL;
-         return -1;
-       }
+        {
+        case SIG_BLOCK:
+          new_blocked_set = blocked_set | *set;
+          break;
+        case SIG_SETMASK:
+          new_blocked_set = *set;
+          break;
+        case SIG_UNBLOCK:
+          new_blocked_set = blocked_set & ~*set;
+          break;
+        default:
+          errno = EINVAL;
+          return -1;
+        }
       to_unblock = blocked_set & ~new_blocked_set;
       to_block = new_blocked_set & ~blocked_set;
 
       if (to_block != 0)
-       {
-         int sig;
-
-         for (sig = 0; sig < NSIG; sig++)
-           if ((to_block >> sig) & 1)
-             {
-               pending_array[sig] = 0;
-               if ((old_handlers[sig] = signal (sig, blocked_handler)) != SIG_ERR)
-                 blocked_set |= 1U << sig;
-             }
-       }
+        {
+          int sig;
+
+          for (sig = 0; sig < NSIG; sig++)
+            if ((to_block >> sig) & 1)
+              {
+                pending_array[sig] = 0;
+                if ((old_handlers[sig] = signal (sig, blocked_handler)) != SIG_ERR)
+                  blocked_set |= 1U << sig;
+              }
+        }
 
       if (to_unblock != 0)
-       {
-         sig_atomic_t received[NSIG];
-         int sig;
-
-         for (sig = 0; sig < NSIG; sig++)
-           if ((to_unblock >> sig) & 1)
-             {
-               if (signal (sig, old_handlers[sig]) != blocked_handler)
-                 /* The application changed a signal handler while the signal
-                    was blocked.  We don't support this.  */
-                 abort ();
-               received[sig] = pending_array[sig];
-               blocked_set &= ~(1U << sig);
-               pending_array[sig] = 0;
-             }
-           else
-             received[sig] = 0;
-
-         for (sig = 0; sig < NSIG; sig++)
-           if (received[sig])
-             {
-               #if HAVE_RAISE
-               raise (sig);
-               #else
-               kill (getpid (), sig);
-               #endif
-             }
-       }
+        {
+          sig_atomic_t received[NSIG];
+          int sig;
+
+          for (sig = 0; sig < NSIG; sig++)
+            if ((to_unblock >> sig) & 1)
+              {
+                if (signal (sig, old_handlers[sig]) != blocked_handler)
+                  /* The application changed a signal handler while the signal
+                     was blocked, bypassing our rpl_signal replacement.
+                     We don't support this.  */
+                  abort ();
+                received[sig] = pending_array[sig];
+                blocked_set &= ~(1U << sig);
+                pending_array[sig] = 0;
+              }
+            else
+              received[sig] = 0;
+
+          for (sig = 0; sig < NSIG; sig++)
+            if (received[sig])
+              raise (sig);
+        }
+    }
+  return 0;
+}
+
+/* Install the handler FUNC for signal SIG, and return the previous
+   handler.  */
+handler_t
+rpl_signal (int sig, handler_t handler)
+{
+  /* We must provide a wrapper, so that a user can query what handler
+     they installed even if that signal is currently blocked.  */
+  if (sig >= 0 && sig < NSIG && sig != SIGKILL && sig != SIGSTOP
+      && handler != SIG_ERR)
+    {
+      #ifdef SIGABRT_COMPAT
+      if (sig == SIGABRT_COMPAT)
+        sig = SIGABRT;
+      #endif
+
+      if (blocked_set & (1U << sig))
+        {
+          /* POSIX states that sigprocmask and signal are both
+             async-signal-safe.  This is not true of our
+             implementation - there is a slight data race where an
+             asynchronous interrupt on signal A can occur after we
+             install blocked_handler but before we have updated
+             old_handlers for signal B, such that handler A can see
+             stale information if it calls signal(B).  Oh well -
+             signal handlers really shouldn't try to manipulate the
+             installed handlers of unrelated signals.  */
+          handler_t result = old_handlers[sig];
+          old_handlers[sig] = handler;
+          return result;
+        }
+      else
+        return signal (sig, handler);
+    }
+  else
+    {
+      errno = EINVAL;
+      return SIG_ERR;
+    }
+}
+
+#if GNULIB_defined_SIGPIPE
+/* Raise the signal SIGPIPE.  */
+int
+_gl_raise_SIGPIPE (void)
+{
+  if (blocked_set & (1U << SIGPIPE))
+    pending_array[SIGPIPE] = 1;
+  else
+    {
+      handler_t handler = SIGPIPE_handler;
+      if (handler == SIG_DFL)
+        exit (128 + SIGPIPE);
+      else if (handler != SIG_IGN)
+        (*handler) (SIGPIPE);
     }
   return 0;
 }
+#endif