From b73c899f3235ad67aa688b988ee20360fe18b1d7 Mon Sep 17 00:00:00 2001 From: "Niall Douglas (s [underscore] sourceforge {at} nedprod [dot] com)" Date: Wed, 12 Feb 2025 15:20:35 +0100 Subject: [PATCH] Global signal deciders for POSIX should now be working. --- .clang-format | 2 +- CMakeLists.txt | 2 + include/wg14_signals/config.h | 13 + include/wg14_signals/current_thread_id.h | 18 +- include/wg14_signals/thrd_signal_handle.h | 188 ++++--- include/wg14_signals/tss_async_signal_safe.h | 31 +- src/wg14_signals/linked_list.h | 79 +++ src/wg14_signals/lock_unlock.h | 21 +- src/wg14_signals/thrd_signal_handle_posix.c | 518 +++++++++++++++++++ test/CMakeLists.txt | 1 + test/thrd_signal_handle_test.c | 42 ++ 11 files changed, 811 insertions(+), 104 deletions(-) create mode 100644 src/wg14_signals/linked_list.h create mode 100644 src/wg14_signals/thrd_signal_handle_posix.c create mode 100644 test/thrd_signal_handle_test.c diff --git a/.clang-format b/.clang-format index c066db5..a1dcb28 100644 --- a/.clang-format +++ b/.clang-format @@ -16,7 +16,7 @@ BreakBeforeBraces: Allman BreakBeforeTernaryOperators: false BreakConstructorInitializersBeforeComma: true BinPackParameters: true -ColumnLimit: 120 +ColumnLimit: 80 CommentPragmas: '^!<' ConstructorInitializerAllOnOneLineOrOnePerLine: false ContinuationIndentWidth: 0 diff --git a/CMakeLists.txt b/CMakeLists.txt index b2be2b6..2a604eb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,6 +12,8 @@ enable_testing() set(LIBRARY_SOURCES "src/wg14_signals/current_thread_id.c" "src/wg14_signals/tss_async_signal_safe.c" + $<$>:src/wg14_signals/thrd_signal_handle_posix.c> + $<$:src/wg14_signals/thrd_signal_handle_windows.c> # Not mandatory and can be substituted "src/wg14_signals/thread_atexit.cpp" diff --git a/include/wg14_signals/config.h b/include/wg14_signals/config.h index c2bc942..9220372 100644 --- a/include/wg14_signals/config.h +++ b/include/wg14_signals/config.h @@ -40,6 +40,14 @@ limitations under the License. #endif #endif +#ifndef WG14_SIGNALS_DEFAULT_VISIBILITY +#ifdef _WIN32 +#define WG14_SIGNALS_DEFAULT_VISIBILITY +#else +#define WG14_SIGNALS_DEFAULT_VISIBILITY __attribute__((visibility("default"))) +#endif +#endif + #ifndef WG14_SIGNALS_EXTERN #if WG14_SIGNALS_SOURCE #ifdef _WIN32 @@ -52,6 +60,11 @@ limitations under the License. #endif #endif +#ifndef WG14_SIGNALS_STDERR_PRINTF +#include +#define WG14_SIGNALS_STDERR_PRINTF(...) fprintf(stderr, __VA_ARGS__) +#endif + #ifdef __cplusplus extern "C" { diff --git a/include/wg14_signals/current_thread_id.h b/include/wg14_signals/current_thread_id.h index ab11d1c..1e2aa18 100644 --- a/include/wg14_signals/current_thread_id.h +++ b/include/wg14_signals/current_thread_id.h @@ -32,23 +32,29 @@ extern "C" //! \brief The type of a thread id typedef uintptr_t WG14_SIGNALS_PREFIX(thread_id_t); - static const WG14_SIGNALS_PREFIX(thread_id_t) WG14_SIGNALS_PREFIX(thread_id_t_tombstone) = 0; + static const WG14_SIGNALS_PREFIX(thread_id_t) + WG14_SIGNALS_PREFIX(thread_id_t_tombstone) = 0; #ifdef _WIN32 static #else WG14_SIGNALS_EXTERN #endif - WG14_SIGNALS_THREAD_LOCAL WG14_SIGNALS_PREFIX(thread_id_t) WG14_SIGNALS_PREFIX(current_thread_id_cached); + WG14_SIGNALS_THREAD_LOCAL WG14_SIGNALS_PREFIX(thread_id_t) + WG14_SIGNALS_PREFIX(current_thread_id_cached); - WG14_SIGNALS_EXTERN WG14_SIGNALS_PREFIX(thread_id_t) WG14_SIGNALS_PREFIX(current_thread_id_cached_set)(void); + WG14_SIGNALS_EXTERN WG14_SIGNALS_PREFIX(thread_id_t) + WG14_SIGNALS_PREFIX(current_thread_id_cached_set)(void); //! \brief THREADSAFE; ASYNC SIGNAL SAFE; Retrieve the current thread id - static WG14_SIGNALS_INLINE WG14_SIGNALS_PREFIX(thread_id_t) WG14_SIGNALS_PREFIX(current_thread_id)(void) + static WG14_SIGNALS_INLINE WG14_SIGNALS_PREFIX(thread_id_t) + WG14_SIGNALS_PREFIX(current_thread_id)(void) { - if(WG14_SIGNALS_PREFIX(current_thread_id_cached) == WG14_SIGNALS_PREFIX(thread_id_t_tombstone)) + if(WG14_SIGNALS_PREFIX(current_thread_id_cached) == + WG14_SIGNALS_PREFIX(thread_id_t_tombstone)) { - WG14_SIGNALS_PREFIX(current_thread_id_cached) = WG14_SIGNALS_PREFIX(current_thread_id_cached_set)(); + WG14_SIGNALS_PREFIX(current_thread_id_cached) = + WG14_SIGNALS_PREFIX(current_thread_id_cached_set)(); } return WG14_SIGNALS_PREFIX(current_thread_id_cached); } diff --git a/include/wg14_signals/thrd_signal_handle.h b/include/wg14_signals/thrd_signal_handle.h index aec0ae1..943c668 100644 --- a/include/wg14_signals/thrd_signal_handle.h +++ b/include/wg14_signals/thrd_signal_handle.h @@ -23,6 +23,8 @@ limitations under the License. #include "config.h" #include +#include +#include #include #ifdef __cplusplus @@ -64,13 +66,16 @@ typedef int WG14_SIGNALS_PREFIX(thrd_raised_signal_error_code_t); */ struct WG14_SIGNALS_PREFIX(thrd_raised_signal_info) { - jmp_buf buf; //!< setjmp() buffer written on entry to guarded section - int signo; //!< The signal raised + const jmp_buf + *buf; //!< setjmp() buffer written on entry to guarded section + int signo; //!< The signal raised - //! The system specific error code for this signal, the `si_errno` code (POSIX) or `NTSTATUS` code (Windows) + //! The system specific error code for this signal, the `si_errno` code + //! (POSIX) or `NTSTATUS` code (Windows) WG14_SIGNALS_PREFIX(thrd_raised_signal_error_code_t) error_code; void *addr; //!< Memory location which caused fault, if appropriate - union WG14_SIGNALS_PREFIX(thrd_raised_signal_info_value) value; //!< A user-defined value + union WG14_SIGNALS_PREFIX( + thrd_raised_signal_info_value) value; //!< A user-defined value //! The OS specific `siginfo_t *` (POSIX) or `PEXCEPTION_RECORD` (Windows) void *raw_info; @@ -79,113 +84,142 @@ typedef int WG14_SIGNALS_PREFIX(thrd_raised_signal_error_code_t); }; //! \brief The type of the guarded function. - typedef union WG14_SIGNALS_PREFIX(thrd_raised_signal_info_value) (*WG14_SIGNALS_PREFIX(thrd_signal_func_t))( + typedef union WG14_SIGNALS_PREFIX(thrd_raised_signal_info_value) ( + *WG14_SIGNALS_PREFIX(thrd_signal_func_t))( union WG14_SIGNALS_PREFIX(thrd_raised_signal_info_value)); - //! \brief The type of the function called to recover from a signal being raised in a guarded section. - typedef union WG14_SIGNALS_PREFIX(thrd_raised_signal_info_value) (*WG14_SIGNALS_PREFIX(thrd_signal_recover_t))( + //! \brief The type of the function called to recover from a signal being + //! raised in a guarded section. + typedef union WG14_SIGNALS_PREFIX(thrd_raised_signal_info_value) ( + *WG14_SIGNALS_PREFIX(thrd_signal_recover_t))( const struct WG14_SIGNALS_PREFIX(thrd_raised_signal_info) *); - //! \brief The type of the function called when a signal is raised. Returns true to continue guarded code, false to - //! recover. - typedef bool (*WG14_SIGNALS_PREFIX(thrd_signal_decide_t))(struct WG14_SIGNALS_PREFIX(thrd_raised_signal_info) *); + //! \brief The type of the function called when a signal is raised. Returns + //! true to continue guarded code, false to recover. + typedef bool (*WG14_SIGNALS_PREFIX(thrd_signal_decide_t))( + struct WG14_SIGNALS_PREFIX(thrd_raised_signal_info) *); #ifdef _MSC_VER #pragma warning(push) #pragma warning(disable : 4190) // C-linkage with UDTs #endif - /*! \brief Installs a thread-local signal guard for the calling thread, and calls the guarded function `guarded`. + /*! \brief Installs a thread-local signal guard for the calling thread, and + calls the guarded function `guarded`. \return The value returned by `guarded`, or `recovery`. \param signals The set of signals to guard against. - \param guarded A function whose execution is to be guarded against signal raises. + \param guarded A function whose execution is to be guarded against signal + raises. \param recovery A function to be called if a signal is raised. - \param decider A function to be called to decide whether to recover from the signal and continue - the execution of the guarded routine, or to abort and call the recovery routine. + \param decider A function to be called to decide whether to recover from the + signal and continue the execution of the guarded routine, or to abort and call + the recovery routine. \param value A value to supply to the guarded routine. */ WG14_SIGNALS_EXTERN union WG14_SIGNALS_PREFIX(thrd_raised_signal_info_value) - WG14_SIGNALS_PREFIX(thrd_signal_call)(const sigset_t *signals, WG14_SIGNALS_PREFIX(thrd_signal_func_t) guarded, - WG14_SIGNALS_PREFIX(thrd_signal_recover_t) recovery, - WG14_SIGNALS_PREFIX(thrd_signal_decide_t) decider, - union WG14_SIGNALS_PREFIX(thrd_raised_signal_info_value) value); + WG14_SIGNALS_PREFIX(thrd_signal_invoke)( + const sigset_t *signals, WG14_SIGNALS_PREFIX(thrd_signal_func_t) guarded, + WG14_SIGNALS_PREFIX(thrd_signal_recover_t) recovery, + WG14_SIGNALS_PREFIX(thrd_signal_decide_t) decider, + union WG14_SIGNALS_PREFIX(thrd_raised_signal_info_value) value); #ifdef _MSC_VER #pragma warning(pop) #endif - /*! \brief Call the currently installed signal handler for a signal (POSIX), or raise a Win32 structured - exception (Windows), returning false if no handler was called due to the currently - installed handler being `SIG_IGN` (POSIX). - - Note that on POSIX, we fetch the currently installed signal handler and try to call it directly. - This allows us to supply custom `raw_info` and `raw_context`, and we do all the things which the signal - handler flags tell us to do beforehand [1]. If the current handler has been defaulted, we - enable the signal and execute `pthread_kill(pthread_self(), signo)` in order to invoke the - default handling. + /*! \brief Call OUR currently installed signal decider for a signal (POSIX), + or raise a Win32 structured exception (Windows), returning false if we have no + decider installed for that signal. + + Note that on POSIX, we fetch OUR currently installed signal decider and call + it directly. This allows us to supply custom `raw_info` and `raw_context`. + Each decider in our chain will be invoked in turn until we reach whatever the + signal handler was when this library was first initialised, and we hand off + to that handler. If that handler was defaulted and the default handling is not + to ignore, we reset the handler installation and execute + `pthread_kill(pthread_self(), signo)` in order to invoke the default handling. + + It is important to note that this call does not raise signals itself except in + that final handling step as just described. Therefore, if your code overwrites + the signal handlers installed by this library with a custom handler, and you + wish to pass on signal handling to this library, this is the right API to call + to do that. + + Note that on Windows, `raw_context` is ignored as there is no way to override + the context thrown with a Win32 structured exception. + */ + WG14_SIGNALS_EXTERN bool + WG14_SIGNALS_PREFIX(thrd_signal_raise)(int signo, void *raw_info, + void *raw_context); - Note that on Windows, `raw_context` is ignored as there is no way to override the context thrown - with a Win32 structured exception. + /*! \brief THREADSAFE On platforms where it is necessary (POSIX), installs, + and potentially enables, the global signal handlers for the signals specified + by `guarded`. Each signal installed is threadsafe reference counted, so this + is safe to call from multiple threads or instantiate multiple times. - [1]: We currently do not implement alternative stack switching. If a handler requests that, we - simply abort the process. Code donations implementing support are welcome. - */ - WG14_SIGNALS_EXTERN bool WG14_SIGNALS_PREFIX(thrd_signal_raise)(int signo, void *raw_info, void *raw_context); + On platforms with better than POSIX global signal support, this function does + nothing. - /*! \brief THREADSAFE On platforms where it is necessary (POSIX), installs, and potentially enables, - the global signal handlers for the signals specified by `guarded`. Each signal installed - is threadsafe reference counted, so this is safe to call from multiple threads or instantiate - multiple times. - - On platforms with better than POSIX global signal support, this function does nothing. + If `guarded` is null, all the standard POSIX signals are used. `version` is + reserved for future use, and should be zero. ## POSIX only - Any existing global signal handlers are replaced with a filtering signal handler, which - checks if the current kernel thread has installed a signal guard, and if so executes the - guard. If no signal guard has been installed for the current kernel thread, global signal - continuation handlers are executed. If none claims the signal, the previously - installed signal handler is called. - - After the new signal handlers have been installed, the guarded signals are globally enabled - for all threads of execution. Be aware that the handlers are installed with `SA_NODEFER` - to avoid the need to perform an expensive syscall when a signal is handled. - However this may also produce surprise e.g. infinite loops. - - \warning This class is threadsafe with respect to other concurrent executions of itself, - but is NOT threadsafe with respect to other code modifying the global signal handlers. + Any existing global signal handlers are replaced with a filtering signal + handler, which checks if the current kernel thread has installed a signal + guard, and if so executes the guard. If no signal guard has been installed for + the current kernel thread, global signal continuation handlers are executed. + If none claims the signal, the previously installed signal handler is called. + + After the new signal handlers have been installed, the guarded signals are + globally enabled for all threads of execution. Be aware that the handlers are + installed with `SA_NODEFER` to avoid the need to perform an expensive syscall + when a signal is handled. However this may also produce surprise e.g. infinite + loops. + + \warning This class is threadsafe with respect to other concurrent executions + of itself, but is NOT threadsafe with respect to other code modifying the + global signal handlers. */ - WG14_SIGNALS_EXTERN void *WG14_SIGNALS_PREFIX(modern_signals_install)(const sigset_t *guarded, int version); + WG14_SIGNALS_EXTERN void * + WG14_SIGNALS_PREFIX(modern_signals_install)(const sigset_t *guarded, + int version); /*! \brief THREADSAFE Uninstall a previously installed signal guard. */ - WG14_SIGNALS_EXTERN bool WG14_SIGNALS_PREFIX(modern_signals_uninstall)(void *i); + WG14_SIGNALS_EXTERN bool + WG14_SIGNALS_PREFIX(modern_signals_uninstall)(void *i); /*! \brief THREADSAFE Uninstall a previously system installed signal guard. */ - WG14_SIGNALS_EXTERN bool WG14_SIGNALS_PREFIX(modern_signals_uninstall_system)(int version); - - /*! \brief THREADSAFE NOT REENTRANT Create a global signal continuation decider. Threadsafe with respect to - other calls of this function, but not reentrant i.e. modifying the global signal continuation - decider registry whilst inside a global signal continuation decider is racy. Called after - all thread local handling is exhausted. Note that what you can safely do in the decider - function is extremely limited, only async signal safe functions may be called. - - \return An opaque pointer to the registered decider. `NULL` if `malloc` failed. + WG14_SIGNALS_EXTERN bool + WG14_SIGNALS_PREFIX(modern_signals_uninstall_system)(int version); + + /*! \brief THREADSAFE NOT REENTRANT Create a global signal continuation + decider. Threadsafe with respect to other calls of this function, but not + reentrant i.e. modifying the global signal continuation decider registry + whilst inside a global signal continuation decider is racy. Called after all + thread local handling is exhausted. Note that what you can safely do in the + decider function is extremely limited, only async signal safe functions may be + called. + + \return An opaque pointer to the registered decider. `NULL` if `malloc` + failed. \param guarded The set of signals to be guarded against. - \param callfirst True if this decider should be called before any other. Otherwise - call order is in the order of addition. - \param decider A decider function, which must return `true` if execution is to resume, - `false` if the next decider function should be called. - \param value A user supplied value to set in the `raised_signal_info` passed to the - decider callback. + \param callfirst True if this decider should be called before any other. + Otherwise call order is in the order of addition. + \param decider A decider function, which must return `true` if execution is to + resume, `false` if the next decider function should be called. + \param value A user supplied value to set in the `raised_signal_info` passed + to the decider callback. */ - WG14_SIGNALS_EXTERN void * - WG14_SIGNALS_PREFIX(signal_decider_create)(const sigset_t *guarded, bool callfirst, - WG14_SIGNALS_PREFIX(thrd_signal_decide_t) decider, - union WG14_SIGNALS_PREFIX(thrd_raised_signal_info_value) value); - /*! \brief THREADSAFE NOT REENTRANT Destroy a global signal continuation decider. Threadsafe with - respect to other calls of this function, but not reentrant i.e. do not call - whilst inside a global signal continuation decider. + WG14_SIGNALS_EXTERN void *WG14_SIGNALS_PREFIX(signal_decider_create)( + const sigset_t *guarded, bool callfirst, + WG14_SIGNALS_PREFIX(thrd_signal_decide_t) decider, + union WG14_SIGNALS_PREFIX(thrd_raised_signal_info_value) value); + /*! \brief THREADSAFE NOT REENTRANT Destroy a global signal continuation + decider. Threadsafe with respect to other calls of this function, but not + reentrant i.e. do not call whilst inside a global signal continuation decider. \return True if recognised and thus removed. */ - WG14_SIGNALS_EXTERN bool WG14_SIGNALS_PREFIX(signal_decider_destroy)(void *decider); + WG14_SIGNALS_EXTERN bool + WG14_SIGNALS_PREFIX(signal_decider_destroy)(void *decider); #ifdef __cplusplus diff --git a/include/wg14_signals/tss_async_signal_safe.h b/include/wg14_signals/tss_async_signal_safe.h index ca1bf16..bc33f04 100644 --- a/include/wg14_signals/tss_async_signal_safe.h +++ b/include/wg14_signals/tss_async_signal_safe.h @@ -28,7 +28,8 @@ extern "C" #endif //! \brief The type of an async signal safe thread local - typedef struct WG14_SIGNALS_PREFIX(tss_async_signal_safe) * WG14_SIGNALS_PREFIX(tss_async_signal_safe); + typedef struct WG14_SIGNALS_PREFIX(tss_async_signal_safe) * + WG14_SIGNALS_PREFIX(tss_async_signal_safe); //! \brief The attributes for creating an async signal safe thread local struct WG14_SIGNALS_PREFIX(tss_async_signal_safe_attr) @@ -43,22 +44,28 @@ extern "C" const struct WG14_SIGNALS_PREFIX(tss_async_signal_safe_attr) * attr); //! \brief Destroy an async signal safe thread local instance - WG14_SIGNALS_EXTERN int WG14_SIGNALS_PREFIX(tss_async_signal_safe_destroy)(WG14_SIGNALS_PREFIX(tss_async_signal_safe) - val); + WG14_SIGNALS_EXTERN int WG14_SIGNALS_PREFIX(tss_async_signal_safe_destroy)( + WG14_SIGNALS_PREFIX(tss_async_signal_safe) val); - /*! \brief THREADSAFE Initialise an async signal safe thread local instance for a specific thread + /*! \brief THREADSAFE Initialise an async signal safe thread local instance + for a specific thread - The initialisation and registration of a thread local state for a specific thread is not async signal safe, and so - must be manually performed by each thread before use of an async signal safe thread local instance. Call this function - somewhere near the beginning of your thread instance. + The initialisation and registration of a thread local state for a specific + thread is not async signal safe, and so must be manually performed by each + thread before use of an async signal safe thread local instance. Call this + function somewhere near the beginning of your thread instance. + + It is safe to call this functions many times in a thread. */ - WG14_SIGNALS_EXTERN int - WG14_SIGNALS_PREFIX(tss_async_signal_safe_thread_init)(WG14_SIGNALS_PREFIX(tss_async_signal_safe) val); + WG14_SIGNALS_EXTERN int WG14_SIGNALS_PREFIX( + tss_async_signal_safe_thread_init)(WG14_SIGNALS_PREFIX(tss_async_signal_safe) + val); - /*! \brief THREADSAFE ASYNC-SIGNAL-SAFE Get the thread local value for the current thread. + /*! \brief THREADSAFE ASYNC-SIGNAL-SAFE Get the thread local value for the + * current thread. */ - WG14_SIGNALS_EXTERN void *WG14_SIGNALS_PREFIX(tss_async_signal_safe_get)(WG14_SIGNALS_PREFIX(tss_async_signal_safe) - val); + WG14_SIGNALS_EXTERN void *WG14_SIGNALS_PREFIX(tss_async_signal_safe_get)( + WG14_SIGNALS_PREFIX(tss_async_signal_safe) val); #ifdef __cplusplus } diff --git a/src/wg14_signals/linked_list.h b/src/wg14_signals/linked_list.h new file mode 100644 index 0000000..0dc188e --- /dev/null +++ b/src/wg14_signals/linked_list.h @@ -0,0 +1,79 @@ +/* Proposed WG14 improved signals support +(C) 2025 Niall Douglas +File Created: Feb 2025 + + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License in the accompanying file +Licence.txt or at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef WG14_SIGNALS_LINKED_LIST_H +#define WG14_SIGNALS_LINKED_LIST_H + +#include "wg14_signals/config.h" + +#include + +#define LIST_INSERT_FRONT(lst, item) \ + if((lst).front == WG14_SIGNALS_NULLPTR) \ + { \ + assert((lst).back == WG14_SIGNALS_NULLPTR); \ + (lst).front = (lst).back = (item); \ + } \ + else \ + { \ + assert((lst).front != WG14_SIGNALS_NULLPTR); \ + assert((lst).back != WG14_SIGNALS_NULLPTR); \ + (item)->prev = WG14_SIGNALS_NULLPTR; \ + (item)->next = (lst).front; \ + (lst).front = (item); \ + } + +#define LIST_INSERT_BACK(lst, item) \ + if((lst).front == WG14_SIGNALS_NULLPTR) \ + { \ + assert((lst).back == WG14_SIGNALS_NULLPTR); \ + (lst).front = (lst).back = (item); \ + } \ + else \ + { \ + assert((lst).front != WG14_SIGNALS_NULLPTR); \ + assert((lst).back != WG14_SIGNALS_NULLPTR); \ + (item)->next = WG14_SIGNALS_NULLPTR; \ + (item)->prev = (lst).back; \ + (lst).back = (item); \ + } + +#define LIST_REMOVE(lst, item) \ + if((lst).front == (item) && (lst).back == (item)) \ + { \ + (lst).front = (lst).back = WG14_SIGNALS_NULLPTR; \ + } \ + else if((lst).front == (item)) \ + { \ + (lst).front = (item)->next; \ + (lst).front->prev = WG14_SIGNALS_NULLPTR; \ + } \ + else if((lst).back == (item)) \ + { \ + (lst).back = (item)->prev; \ + (lst).back->next = WG14_SIGNALS_NULLPTR; \ + } \ + else \ + { \ + (item)->next->prev = (item)->prev; \ + (item)->prev->next = (item)->next; \ + } \ + (item)->next = (item)->prev = WG14_SIGNALS_NULLPTR; + +#endif diff --git a/src/wg14_signals/lock_unlock.h b/src/wg14_signals/lock_unlock.h index b2e64f4..705cef1 100644 --- a/src/wg14_signals/lock_unlock.h +++ b/src/wg14_signals/lock_unlock.h @@ -22,14 +22,19 @@ limitations under the License. #include "wg14_signals/config.h" -#define LOCK(x) \ - for(;;) \ - { \ - unsigned expected = 0; \ - if(atomic_compare_exchange_weak_explicit(&(x), &expected, 1, memory_order_acq_rel, memory_order_relaxed)) \ - { \ - break; \ - } \ +#define LOCK(x) \ + for(;;) \ + { \ + if(atomic_load_explicit(&(x), memory_order_relaxed)) \ + { \ + continue; \ + } \ + unsigned expected = 0; \ + if(atomic_compare_exchange_weak_explicit( \ + &(x), &expected, 1, memory_order_acq_rel, memory_order_relaxed)) \ + { \ + break; \ + } \ } #define UNLOCK(x) atomic_store_explicit(&(x), 0, memory_order_release) diff --git a/src/wg14_signals/thrd_signal_handle_posix.c b/src/wg14_signals/thrd_signal_handle_posix.c new file mode 100644 index 0000000..e5aa746 --- /dev/null +++ b/src/wg14_signals/thrd_signal_handle_posix.c @@ -0,0 +1,518 @@ +/* Proposed WG14 improved signals support +(C) 2025 Niall Douglas +File Created: Feb 2025 + + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License in the accompanying file +Licence.txt or at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "wg14_signals/thrd_signal_handle.h" + +#include "linked_list.h" +#include "lock_unlock.h" +#include "wg14_signals/tss_async_signal_safe.h" + +#include +#include +#include +#include +#include + +#include + +struct sighandler_t; + +#if NSIG < 1024 +typedef struct signo_to_sighandler_map_t +{ + struct sighandler_t *arr[NSIG]; +} signo_to_sighandler_map_t; + +struct signo_to_sighandler_map_t_itr_data +{ + struct sighandler_t *val; + size_t idx; +}; +typedef struct signo_to_sighandler_map_t_itr +{ + struct signo_to_sighandler_map_t_itr_data data_, *data; +} signo_to_sighandler_map_t_itr; + +static inline signo_to_sighandler_map_t_itr +signo_to_sighandler_map_t_get(signo_to_sighandler_map_t *map, int idx) +{ + signo_to_sighandler_map_t_itr ret; + if(idx < 0 || idx >= NSIG || map->arr[idx] == WG14_SIGNALS_NULLPTR) + { + ret.data = WG14_SIGNALS_NULLPTR; + return ret; + } + ret.data_.val = map->arr[idx]; + ret.data_.idx = idx; + ret.data = &ret.data_; + return ret; +} +static inline bool +signo_to_sighandler_map_t_is_end(signo_to_sighandler_map_t_itr it) +{ + return it.data == WG14_SIGNALS_NULLPTR; +} +static inline signo_to_sighandler_map_t_itr +signo_to_sighandler_map_t_insert(signo_to_sighandler_map_t *map, int idx, + struct sighandler_t *val) +{ + signo_to_sighandler_map_t_itr ret; + if(idx < 0 || idx >= NSIG) + { + ret.data = WG14_SIGNALS_NULLPTR; + return ret; + } + assert(map->arr[idx] == WG14_SIGNALS_NULLPTR); + map->arr[idx] = val; + ret.data_.val = map->arr[idx]; + ret.data_.idx = idx; + ret.data = &ret.data_; + return ret; +} +static inline void +signo_to_sighandler_map_t_erase_itr(signo_to_sighandler_map_t *map, + signo_to_sighandler_map_t_itr it) +{ + assert(it.data->idx >= 0 && it.data->idx < NSIG); + map->arr[it.data->idx] = WG14_SIGNALS_NULLPTR; +} +#else +#define NAME signo_to_sighandler_map_t +#define KEY_TY int +#define VAL_TY struct sighandler_t * +#include "verstable.h" +#endif + +/**********************************************************************************/ + +struct global_signal_decider_t +{ + struct global_signal_decider_t *prev, *next; + + WG14_SIGNALS_PREFIX(thrd_signal_decide_t) decider; + union WG14_SIGNALS_PREFIX(thrd_raised_signal_info_value) value; +}; + +struct sighandler_t +{ + int count; + struct sigaction old_handler; + WG14_SIGNALS_PREFIX(tss_async_signal_safe) thread_handler; + struct + { + struct global_signal_decider_t *front, *back; + } global_handler; +}; + +struct WG14_SIGNALS_PREFIX(thrd_signal_global_state_t) +{ + atomic_uint lock; + signo_to_sighandler_map_t signo_to_sighandler_map; +}; +WG14_SIGNALS_EXTERN struct WG14_SIGNALS_PREFIX(thrd_signal_global_state_t) * +WG14_SIGNALS_PREFIX(thrd_signal_global_state)(void) +{ + static struct WG14_SIGNALS_PREFIX(thrd_signal_global_state_t) v; + return &v; +} + +#if 0 +// Reset the SIGABRT handler, and call abort() +static void __attribute__((noreturn)) default_abort(void) +{ + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = SIG_DFL; + (void) sigaction(SIGABRT, &sa, WG14_SIGNALS_NULLPTR); + abort(); +} +#endif + +// Invoke a sigaction as if it were the first signal handler +static bool invoke_sigaction(const struct sigaction *sa, const int signo, + siginfo_t *siginfo, void *context) +{ + if((sa->sa_flags & SA_SIGINFO) != 0) + { + sa->sa_sigaction(signo, siginfo, context); + return true; + } + if(sa->sa_handler == SIG_DFL) + { + switch(signo) + { + case SIGCHLD: + case SIGURG: +#ifdef SIGWINCH + case SIGWINCH: +#endif + // The default is to ignore + return false; + default: + { + // The default is to abort, so reset the signal handler + // to default and send ourselves the signal + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = SIG_DFL; + (void) sigaction(signo, &sa, WG14_SIGNALS_NULLPTR); + (void) pthread_kill(pthread_self(), signo); + return true; + } + } + } + if(sa->sa_handler == SIG_IGN) + { + // We do nothing + return false; + } + sa->sa_handler(signo); + return true; +} + +// The base signal handler for POSIX +// You must NOT do anything async signal unsafe in here! +static void raw_signal_handler(int signo, siginfo_t *siginfo, void *context) +{ + if(!WG14_SIGNALS_PREFIX(thrd_signal_raise)(signo, siginfo, context)) + { + // It shouldn't happen that this handler gets called when we have no + // knowledge of the signal. We could default_abort() here, but it seems more + // reliable to remove the installation of ourselves and pass on the signal + // there + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = SIG_DFL; + invoke_sigaction(&sa, signo, siginfo, context); + } +} + +union WG14_SIGNALS_PREFIX(thrd_raised_signal_info_value) +WG14_SIGNALS_PREFIX(thrd_signal_invoke)( +const sigset_t *signals, WG14_SIGNALS_PREFIX(thrd_signal_func_t) guarded, +WG14_SIGNALS_PREFIX(thrd_signal_recover_t) recovery, +WG14_SIGNALS_PREFIX(thrd_signal_decide_t) decider, +union WG14_SIGNALS_PREFIX(thrd_raised_signal_info_value) value) +{ + (void) guarded; + (void) signals; + (void) recovery; + (void) decider; + (void) value; + abort(); +} + +// You must NOT do anything async signal unsafe in here! +bool WG14_SIGNALS_PREFIX(thrd_signal_raise)(int signo, void *raw_info, + void *raw_context) +{ + struct WG14_SIGNALS_PREFIX(thrd_signal_global_state_t) *state = + WG14_SIGNALS_PREFIX(thrd_signal_global_state)(); + LOCK(state->lock); + signo_to_sighandler_map_t_itr it = + signo_to_sighandler_map_t_get(&state->signo_to_sighandler_map, signo); + if(signo_to_sighandler_map_t_is_end(it)) + { + // We don't have a handler installed for that signal + UNLOCK(state->lock); + return false; + } + struct sigaction sa = it.data->val->old_handler; + struct WG14_SIGNALS_PREFIX(thrd_raised_signal_info) rsi; + memset(&rsi, 0, sizeof(rsi)); + rsi.signo = signo; + rsi.raw_context = raw_context; + if(raw_info != WG14_SIGNALS_NULLPTR) + { + rsi.raw_info = raw_info; + siginfo_t *siginfo = (siginfo_t *) raw_info; + rsi.error_code = siginfo->si_errno; + rsi.addr = siginfo->si_addr; + } + if(it.data->val->global_handler.front != WG14_SIGNALS_NULLPTR) + { + struct global_signal_decider_t *current = + it.data->val->global_handler.front; + do + { + rsi.value = current->value; + UNLOCK(state->lock); + if(current->decider(&rsi)) + { + return true; + } + LOCK(state->lock); + current = current->next; + } while(current != WG14_SIGNALS_NULLPTR); + } + // None of our deciders want this, so call previously installed signal handler + UNLOCK(state->lock); + invoke_sigaction(&sa, signo, (siginfo_t *) raw_info, raw_context); + return true; +} + +static bool install_sighandler(const int signo) +{ + struct WG14_SIGNALS_PREFIX(thrd_signal_global_state_t) *state = + WG14_SIGNALS_PREFIX(thrd_signal_global_state)(); + LOCK(state->lock); + signo_to_sighandler_map_t_itr it = + signo_to_sighandler_map_t_get(&state->signo_to_sighandler_map, signo); + if(signo_to_sighandler_map_t_is_end(it)) + { + struct sighandler_t *newitem = + (struct sighandler_t *) calloc(1, sizeof(struct sighandler_t)); + if(newitem == WG14_SIGNALS_NULLPTR) + { + UNLOCK(state->lock); + return false; + } + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sa.sa_sigaction = raw_signal_handler; + sa.sa_flags = SA_SIGINFO | SA_NOCLDWAIT | SA_NODEFER; + if(-1 == sigaction(signo, &sa, &newitem->old_handler)) + { + int errcode = errno; + free(newitem); + errno = errcode; + return false; + } + it = signo_to_sighandler_map_t_insert(&state->signo_to_sighandler_map, + signo, newitem); + } + it.data->val->count++; + UNLOCK(state->lock); + return true; +} + +static bool uninstall_sighandler(const int signo) +{ + struct WG14_SIGNALS_PREFIX(thrd_signal_global_state_t) *state = + WG14_SIGNALS_PREFIX(thrd_signal_global_state)(); + LOCK(state->lock); + signo_to_sighandler_map_t_itr it = + signo_to_sighandler_map_t_get(&state->signo_to_sighandler_map, signo); + if(!signo_to_sighandler_map_t_is_end(it)) + { + if(0 == --it.data->val->count) + { + (void) sigaction(signo, &it.data->val->old_handler, WG14_SIGNALS_NULLPTR); + free(it.data->val); + signo_to_sighandler_map_t_erase_itr(&state->signo_to_sighandler_map, it); + } + } + UNLOCK(state->lock); + return true; +} + +void *WG14_SIGNALS_PREFIX(modern_signals_install)(const sigset_t *guarded, + int version) +{ + if(version != 0) + { + errno = EINVAL; + return WG14_SIGNALS_NULLPTR; + } + sigset_t *ret = malloc(sizeof(sigset_t)); + if(ret == WG14_SIGNALS_NULLPTR) + { + return WG14_SIGNALS_NULLPTR; + } + if(guarded == WG14_SIGNALS_NULLPTR) + { + sigfillset(ret); + } + else + { + *ret = *guarded; + } + for(int signo = 1; signo < NSIG; signo++) + { + if(signo == SIGKILL || signo == SIGSTOP) + { + continue; + } + if(sigismember(ret, signo)) + { + if(!install_sighandler(signo)) + { + const int errcode = errno; + free(ret); + errno = errcode; + return WG14_SIGNALS_NULLPTR; + } + } + } + return ret; +} + +bool WG14_SIGNALS_PREFIX(modern_signals_uninstall)(void *ss) +{ + sigset_t *sigset = (sigset_t *) ss; + for(int signo = 1; signo < NSIG; signo++) + { + if(signo == SIGKILL || signo == SIGSTOP) + { + continue; + } + if(sigismember(sigset, signo)) + { + if(!uninstall_sighandler(signo)) + { + return false; + } + } + } + return true; +} + +bool WG14_SIGNALS_PREFIX(modern_signals_uninstall_system)(int version) +{ + if(version != 0) + { + errno = EINVAL; + return false; + } + return true; +} + +void *WG14_SIGNALS_PREFIX(signal_decider_create)( +const sigset_t *guarded, bool callfirst, +WG14_SIGNALS_PREFIX(thrd_signal_decide_t) decider, +union WG14_SIGNALS_PREFIX(thrd_raised_signal_info_value) value) +{ + size_t signo_count = 0; + for(int signo = 1; signo < NSIG; signo++) + { + if(signo == SIGKILL || signo == SIGSTOP) + { + continue; + } + if(sigismember(guarded, signo)) + { + signo_count++; + } + } + if(signo_count == 0 || decider == WG14_SIGNALS_NULLPTR) + { + errno = EINVAL; + return WG14_SIGNALS_NULLPTR; + } + void *ret = malloc(sizeof(sigset_t) + + signo_count * sizeof(struct global_signal_decider_t *)); + if(ret == WG14_SIGNALS_NULLPTR) + { + return WG14_SIGNALS_NULLPTR; + } + memset(ret, 0, + sizeof(sigset_t) + + (signo_count + 1) * sizeof(struct global_signal_decider_t *)); + *(sigset_t *) ret = *guarded; + struct global_signal_decider_t **retp = + (struct global_signal_decider_t **) ((char *) ret + sizeof(sigset_t)); + + struct WG14_SIGNALS_PREFIX(thrd_signal_global_state_t) *state = + WG14_SIGNALS_PREFIX(thrd_signal_global_state)(); + for(int signo = 1; signo < NSIG; signo++) + { + if(signo == SIGKILL || signo == SIGSTOP) + { + continue; + } + if(sigismember(guarded, signo)) + { + LOCK(state->lock); + signo_to_sighandler_map_t_itr it = + signo_to_sighandler_map_t_get(&state->signo_to_sighandler_map, signo); + if(signo_to_sighandler_map_t_is_end(it)) + { + // We don't have a handler installed for that signal + WG14_SIGNALS_STDERR_PRINTF( + "WARNING: signal_decider_create() installing decider for signal %d but " + "handler was never installed for that signal.\n", + signo); + continue; + } + struct global_signal_decider_t *i = + (struct global_signal_decider_t *) calloc( + 1, sizeof(struct global_signal_decider_t)); + if(i == WG14_SIGNALS_NULLPTR) + { + int errcode = errno; + UNLOCK(state->lock); + WG14_SIGNALS_PREFIX(signal_decider_destroy)(ret); + errno = errcode; + return WG14_SIGNALS_NULLPTR; + } + i->decider = decider; + i->value = value; + if(callfirst) + { + LIST_INSERT_FRONT(it.data->val->global_handler, i); + } + else + { + LIST_INSERT_BACK(it.data->val->global_handler, i); + } + *retp++ = i; + } + UNLOCK(state->lock); + } + return ret; +} + +bool WG14_SIGNALS_PREFIX(signal_decider_destroy)(void *p) +{ + bool ret = false; + struct WG14_SIGNALS_PREFIX(thrd_signal_global_state_t) *state = + WG14_SIGNALS_PREFIX(thrd_signal_global_state)(); + const sigset_t *guarded = (const sigset_t *) p; + struct global_signal_decider_t **retp = + (struct global_signal_decider_t **) ((char *) p + sizeof(sigset_t)); + for(int signo = 1; signo < NSIG; signo++) + { + if(signo == SIGKILL || signo == SIGSTOP) + { + continue; + } + if(sigismember(guarded, signo)) + { + LOCK(state->lock); + signo_to_sighandler_map_t_itr it = + signo_to_sighandler_map_t_get(&state->signo_to_sighandler_map, signo); + if(!signo_to_sighandler_map_t_is_end(it)) + { + if(*retp != WG14_SIGNALS_NULLPTR) + { + LIST_REMOVE(it.data->val->global_handler, *retp); + ret = true; + } + } + if(*retp != WG14_SIGNALS_NULLPTR) + { + free(*retp); + } + retp++; + UNLOCK(state->lock); + } + } + free(p); + return ret; +} diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index c821ce7..86bf83f 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1 +1,2 @@ add_code_test(async_signal_safe_tls_test SOURCES "async_signal_safe_tls_test.c" FEATURES c_std_11) +add_code_test(thrd_signal_handle_test SOURCES "thrd_signal_handle_test.c" FEATURES c_std_11) diff --git a/test/thrd_signal_handle_test.c b/test/thrd_signal_handle_test.c new file mode 100644 index 0000000..de5e450 --- /dev/null +++ b/test/thrd_signal_handle_test.c @@ -0,0 +1,42 @@ +#include "test_common.h" + +#include "wg14_signals/thrd_signal_handle.h" + +struct shared_t +{ + int count; +}; + +static bool +sigill_decider_func(struct WG14_SIGNALS_PREFIX(thrd_raised_signal_info) * rsi) +{ + struct shared_t *shared = (struct shared_t *) rsi->value.ptr_value; + shared->count++; + return true; // handled +} + +int main() +{ + int ret = 0; + void *handlers = + WG14_SIGNALS_PREFIX(modern_signals_install)(WG14_SIGNALS_NULLPTR, 0); + + { + struct shared_t shared = {.count = 0}; + union WG14_SIGNALS_PREFIX(thrd_raised_signal_info_value) + value = {.ptr_value = &shared}; + sigset_t guarded; + sigemptyset(&guarded); + sigaddset(&guarded, SIGILL); + void *sigill_decider = WG14_SIGNALS_PREFIX(signal_decider_create)( + &guarded, false, sigill_decider_func, value); + CHECK(WG14_SIGNALS_PREFIX(thrd_signal_raise)(SIGILL, WG14_SIGNALS_NULLPTR, + WG14_SIGNALS_NULLPTR)); + CHECK(shared.count == 1); + WG14_SIGNALS_PREFIX(signal_decider_destroy(sigill_decider)); + } + + CHECK(WG14_SIGNALS_PREFIX(modern_signals_uninstall)(handlers)); + printf("Exiting main with result %d ...\n", ret); + return ret; +} \ No newline at end of file