| /* POSIX compatible signal blocking. | |
| Copyright (C) 2006-2026 Free Software Foundation, Inc. | |
| Written by Bruno Haible <bruno@clisp.org>, 2006. | |
| 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> | |
| #include "glthread/lock.h" | |
| #if HAVE_MSVC_INVALID_PARAMETER_HANDLER | |
| # include "msvc-inval.h" | |
| #endif | |
| /* 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 __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 | |
| /* This lock protects the variables defined in this file from concurrent | |
| modification in multiple threads. */ | |
| gl_lock_define_initialized(static, sig_lock) | |
| /* 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) | |
| { | |
| #ifdef SIGABRT_COMPAT | |
| if (sig == SIGABRT_COMPAT) | |
| sig = SIGABRT; | |
| #endif | |
| return (*set >> sig) & 1; | |
| } | |
| else | |
| return 0; | |
| } | |
| int | |
| sigemptyset (sigset_t *set) | |
| { | |
| *set = 0; | |
| return 0; | |
| } | |
| int | |
| 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; | |
| } | |
| else | |
| { | |
| errno = EINVAL; | |
| return -1; | |
| } | |
| } | |
| int | |
| 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; | |
| } | |
| else | |
| { | |
| errno = EINVAL; | |
| return -1; | |
| } | |
| } | |
| int | |
| sigfillset (sigset_t *set) | |
| { | |
| *set = ((2U << (NSIG - 1)) - 1) & ~ SIGABRT_COMPAT_MASK; | |
| return 0; | |
| } | |
| /* Set of currently blocked signals. */ | |
| static sigset_t blocked_set /* = 0 */; | |
| /* Set of currently blocked and pending signals. */ | |
| static volatile sig_atomic_t pending_array[NSIG] /* = { 0 } */; | |
| /* Signal handler that is installed for blocked signals. */ | |
| 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; | |
| } | |
| int | |
| sigpending (sigset_t *set) | |
| { | |
| sigset_t pending = 0; | |
| for (int sig = 0; sig < NSIG; sig++) | |
| if (pending_array[sig]) | |
| pending |= 1U << sig; | |
| *set = pending; | |
| return 0; | |
| } | |
| /* The previous signal handlers. | |
| Only the array elements corresponding to blocked signals are relevant. */ | |
| static volatile handler_t old_handlers[NSIG]; | |
| int | |
| sigprocmask (int operation, const sigset_t *set, sigset_t *old_set) | |
| { | |
| gl_lock_lock (sig_lock); | |
| if (old_set != NULL) | |
| *old_set = blocked_set; | |
| if (set != NULL) | |
| { | |
| sigset_t new_blocked_set; | |
| 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: | |
| gl_lock_unlock (sig_lock); | |
| errno = EINVAL; | |
| return -1; | |
| } | |
| sigset_t to_unblock = blocked_set & ~new_blocked_set; | |
| sigset_t to_block = new_blocked_set & ~blocked_set; | |
| if (to_block != 0) | |
| for (int 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]; | |
| for (int 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 (int sig = 0; sig < NSIG; sig++) | |
| if (received[sig]) | |
| raise (sig); | |
| } | |
| } | |
| gl_lock_unlock (sig_lock); | |
| 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) | |
| { | |
| gl_lock_lock (sig_lock); | |
| if (blocked_set & (1U << SIGPIPE)) | |
| { | |
| pending_array[SIGPIPE] = 1; | |
| gl_lock_unlock (sig_lock); | |
| } | |
| else | |
| { | |
| handler_t handler = SIGPIPE_handler; | |
| gl_lock_unlock (sig_lock); | |
| if (handler == SIG_DFL) | |
| exit (128 + SIGPIPE); | |
| else if (handler != SIG_IGN) | |
| (*handler) (SIGPIPE); | |
| } | |
| return 0; | |
| } | |
| #endif |