diff --git a/ChangeLog.md b/ChangeLog.md index 7d944e865174e..d1e817b993a92 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -20,6 +20,10 @@ See docs/process.md for more on how version tagging works. 2.0.27 ------ +- Added some support for signal handling libc functions (raise, kill, + sigaction, sigpending, etc). We still don't have a way to deliver signals from + the outside but these at least now work for sending signals to the current + thread (JS context) (#14883). - Added `EM_ASYNC_JS` macro - similar to `EM_JS`, but allows using `await` inside the JS block and automatically integrates with Asyncify without the need for listing the declared function in `ASYNCIFY_IMPORTS` (#9709). diff --git a/src/library.js b/src/library.js index 19b6d4ea7b80f..e7d9271350d10 100644 --- a/src/library.js +++ b/src/library.js @@ -2619,6 +2619,20 @@ LibraryManager.library = { return 0; }, + // http://pubs.opengroup.org/onlinepubs/000095399/functions/alarm.html + alarm__deps: ['raise'], + alarm: function(seconds) { + setTimeout(function() { + _raise({{{ cDefine('SIGALRM') }}}); + }, seconds*1000); + }, + + // Helper for raise() to avoid signature mismatch failures: + // https://github.com/emscripten-core/posixtestsuite/issues/6 + __call_sighandler: function(fp, sig) { + {{{ makeDynCall('vi', 'fp') }}}(sig); + }, + // ========================================================================== // emscripten.h // ========================================================================== @@ -3681,6 +3695,12 @@ LibraryManager.library = { }, #endif + $safeSetTimeout__deps: ['$callUserCallback', +#if !MINIMAL_RUNTIME + '$runtimeKeepalivePush', + '$runtimeKeepalivePop', +#endif + ], $safeSetTimeout: function(func, timeout) { {{{ runtimeKeepalivePush() }}} return setTimeout(function() { diff --git a/src/library_signals.js b/src/library_signals.js deleted file mode 100644 index eb23f480bd110..0000000000000 --- a/src/library_signals.js +++ /dev/null @@ -1,85 +0,0 @@ -/** - * @license - * Copyright 2014 The Emscripten Authors - * SPDX-License-Identifier: MIT - */ - -// 'use strict' -var LibrarySignals = { - _sigalrm_handler: 0, - - __sigaction__deps: ['_sigalrm_handler'], - __sigaction: function(sig, act, oldact) { - //int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact); - if (sig == {{{ cDefine('SIGALRM') }}}) { - __sigalrm_handler = {{{ makeGetValue('act', '0', 'i32') }}}; - return 0; - } -#if ASSERTIONS - err('sigaction: signal type not supported: this is a no-op.'); -#endif - return 0; - }, - sigaction__sig: 'viii', - sigaction: '__sigaction', - - // pthread_sigmask - examine and change mask of blocked signals - pthread_sigmask: function(how, set, oldset) { - err('pthread_sigmask() is not supported: this is a no-op.'); - return 0; - }, - - kill__deps: ['$setErrNo'], - kill: function(pid, sig) { - // http://pubs.opengroup.org/onlinepubs/000095399/functions/kill.html - // Makes no sense in a single-process environment. - // Should kill itself somtimes depending on `pid` -#if ASSERTIONS - err('Calling stub instead of kill()'); -#endif - setErrNo({{{ cDefine('EPERM') }}}); - return -1; - }, - - siginterrupt: function() { -#if ASSERTIONS - err('Calling stub instead of siginterrupt()'); -#endif - return 0; - }, - - raise__deps: ['$setErrNo'], - raise: function(sig) { -#if ASSERTIONS - err('Calling stub instead of raise()'); -#endif - setErrNo({{{ cDefine('ENOSYS') }}}); - return -1; - }, - - // http://pubs.opengroup.org/onlinepubs/000095399/functions/alarm.html - alarm__deps: ['_sigalrm_handler'], - alarm: function(seconds) { - setTimeout(function() { - if (__sigalrm_handler) {{{ makeDynCall('vi', '__sigalrm_handler') }}}(0); - }, seconds*1000); - }, - - sigpending: function(set) { - {{{ makeSetValue('set', 0, 0, 'i32') }}}; - return 0; - }, - - //int sigtimedwait(const sigset_t *restrict mask, siginfo_t *restrict si, const struct timespec *restrict timeout) - sigtimedwait: function(set, sig, timeout) { - // POSIX SIGNALS are not supported - // if set contains an invalid signal number, EINVAL is returned - // in our case we return EINVAL all the time -#if ASSERTIONS - err('Calling stub instead of sigwait()'); -#endif - return {{{ cDefine('EINVAL') }}}; - } -}; - -mergeInto(LibraryManager.library, LibrarySignals); diff --git a/src/modules.js b/src/modules.js index d4f8efca9ebe4..a1167ba9bf44d 100644 --- a/src/modules.js +++ b/src/modules.js @@ -61,7 +61,6 @@ var LibraryManager = { 'library_formatString.js', 'library_math.js', 'library_path.js', - 'library_signals.js', 'library_syscall.js', 'library_html5.js', 'library_stack_trace.js', diff --git a/system/lib/libc/kill.c b/system/lib/libc/kill.c new file mode 100644 index 0000000000000..e21537d3c84e1 --- /dev/null +++ b/system/lib/libc/kill.c @@ -0,0 +1,19 @@ +/* + * Copyright 2021 The Emscripten Authors. All rights reserved. + * Emscripten is available under two separate licenses, the MIT license and the + * University of Illinois/NCSA Open Source License. Both these licenses can be + * found in the LICENSE file. + */ + +#include +#include +#include +#include + +int kill(pid_t pid, int sig) { + if (pid == getpid()) { + return raise(sig); + } + errno = EPERM; + return -1; +} diff --git a/system/lib/libc/musl/include/signal.h b/system/lib/libc/musl/include/signal.h index a16094c515694..e206f7b2c224d 100644 --- a/system/lib/libc/musl/include/signal.h +++ b/system/lib/libc/musl/include/signal.h @@ -259,7 +259,7 @@ int sigandset(sigset_t *, const sigset_t *, const sigset_t *); #define SIG_ERR ((void (*)(int))-1) #define SIG_DFL ((void (*)(int)) 0) -#define SIG_IGN ((void (*)(int)) 1) +#define SIG_IGN ((void (*)(int))-2) /* XXX EMSCRIPTEN: use -2 since 1 is a valid function address */ typedef int sig_atomic_t; diff --git a/system/lib/libc/pthread_sigmask.c b/system/lib/libc/pthread_sigmask.c new file mode 100644 index 0000000000000..f0f1373712363 --- /dev/null +++ b/system/lib/libc/pthread_sigmask.c @@ -0,0 +1,70 @@ +/* + * Copyright 2021 The Emscripten Authors. All rights reserved. + * Emscripten is available under two separate licenses, the MIT license and the + * University of Illinois/NCSA Open Source License. Both these licenses can be + * found in the LICENSE file. + */ + +#define _GNU_SOURCE // for sigorset/sigandset +#include +#include +#include +#include +#include "libc.h" + +#define SST_SIZE (_NSIG/8/sizeof(long)) + +static thread_local sigset_t __sig_mask; +sigset_t __sig_pending; + +static int siginvertset(sigset_t *dest, const sigset_t *src) { + unsigned long i = 0, *d = (void*) dest, *s = (void*) src; + for(; i < SST_SIZE; i++) d[i] = ~s[i]; + return 0; +} + +bool __sig_is_blocked(int sig) { + return sigismember(&__sig_mask, sig); +} + +int pthread_sigmask(int how, const sigset_t *restrict set, sigset_t *restrict old) { + if (old) { + *old = __sig_mask; + } + + switch (how) { + case SIG_SETMASK: + __sig_mask = *set; + break; + case SIG_BLOCK: + sigorset(&__sig_mask, &__sig_mask, set); + break; + case SIG_UNBLOCK: { + sigset_t tmp; + siginvertset(&tmp, set); + sigandset(&__sig_mask, &__sig_mask, &tmp); + break; + } + default: + return EINVAL; + } + + // These two signals can never be blocked. + sigdelset(&__sig_mask, SIGKILL); + sigdelset(&__sig_mask, SIGSTOP); + + // Raise any pending signals that are now unblocked. + for (int sig = 0; sig < _NSIG; sig++) { + if (sigismember(&__sig_pending, sig) && !sigismember(&__sig_mask, sig)) { + sigdelset(&__sig_pending, sig); + raise(sig); + } + } + + return 0; +} + +int sigpending(sigset_t *set) { + *set = __sig_pending; + return 0; +} diff --git a/system/lib/libc/raise.c b/system/lib/libc/raise.c new file mode 100644 index 0000000000000..dbd9f1cde0289 --- /dev/null +++ b/system/lib/libc/raise.c @@ -0,0 +1,39 @@ +/* + * Copyright 2021 The Emscripten Authors. All rights reserved. + * Emscripten is available under two separate licenses, the MIT license and the + * University of Illinois/NCSA Open Source License. Both these licenses can be + * found in the LICENSE file. + */ + +#define _GNU_SOURCE // for sighandler_t +#include +#include +#include + +extern struct sigaction __sig_actions[_NSIG]; +extern sigset_t __sig_pending; + +bool __sig_is_blocked(int sig); + +extern void __call_sighandler(sighandler_t handler, int sig); + +int raise(int sig) { + if (__sig_is_blocked(sig)) { + sigaddset(&__sig_pending, sig); + return 0; + } + if (__sig_actions[sig].sa_flags & SA_SIGINFO) { + siginfo_t t = {0}; + __sig_actions[sig].sa_sigaction(sig, &t, NULL); + } else { + if (__sig_actions[sig].sa_handler == SIG_DFL || __sig_actions[sig].sa_handler == SIG_IGN) { + + return 0; + } + // Avoid a direct call to the handler, and instead call via JS so we can + // avoid strict signature checking. + // https://github.com/emscripten-core/posixtestsuite/issues/6 + __call_sighandler(__sig_actions[sig].sa_handler, sig); + } + return 0; +} diff --git a/system/lib/libc/sigaction.c b/system/lib/libc/sigaction.c new file mode 100644 index 0000000000000..e76456559a848 --- /dev/null +++ b/system/lib/libc/sigaction.c @@ -0,0 +1,29 @@ +/* + * Copyright 2021 The Emscripten Authors. All rights reserved. + * Emscripten is available under two separate licenses, the MIT license and the + * University of Illinois/NCSA Open Source License. Both these licenses can be + * found in the LICENSE file. + */ + +#include +#include +#include +#include "libc.h" + +struct sigaction __sig_actions[_NSIG]; + +int __sigaction(int sig, const struct sigaction *restrict sa, struct sigaction *restrict old) { + if (sig < 0 || sig >= _NSIG) { + errno = EINVAL; + return -1; + } + + if (old) { + *old = __sig_actions[sig]; + } + + __sig_actions[sig] = *sa; + return 0; +} + +weak_alias(__sigaction, sigaction); diff --git a/system/lib/libc/sigtimedwait.c b/system/lib/libc/sigtimedwait.c new file mode 100644 index 0000000000000..bccedd87323ed --- /dev/null +++ b/system/lib/libc/sigtimedwait.c @@ -0,0 +1,29 @@ +/* + * Copyright 2021 The Emscripten Authors. All rights reserved. + * Emscripten is available under two separate licenses, the MIT license and the + * University of Illinois/NCSA Open Source License. Both these licenses can be + * found in the LICENSE file. + */ + +#include +#include +#include "syscall.h" +#include "libc.h" + +extern sigset_t __sig_pending; + +int sigtimedwait(const sigset_t *restrict mask, siginfo_t *restrict si, const struct timespec *restrict timeout) { + for (int sig = 0; sig < _NSIG; sig++) { + if (sigismember(mask, sig) && sigismember(&__sig_pending, sig)) { + if (si) { + siginfo_t t = {0}; + *si = t; + } + sigdelset(&__sig_pending, sig); + return sig; + } + } + + errno = EINVAL; + return -1; +} diff --git a/tests/test_core.py b/tests/test_core.py index 9884f94a382ca..e10cddada9fc6 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -5319,6 +5319,9 @@ def test_fs_64bit(self): def test_sigalrm(self): self.do_runf(test_file('test_sigalrm.c'), 'Received alarm!') + def test_signals(self): + self.do_core_test(test_file('test_signals.c')) + @no_windows('https://github.com/emscripten-core/emscripten/issues/8882') def test_unistd_access(self): self.uses_es6 = True diff --git a/tests/test_posixtest.py b/tests/test_posixtest.py index 76a29e40038ee..7067284fa2c1f 100644 --- a/tests/test_posixtest.py +++ b/tests/test_posixtest.py @@ -29,8 +29,6 @@ class posixtest(RunnerCore): def filter_tests(all_tests): pthread_tests = [t for t in all_tests if t.startswith('pthread_')] - # filter out some tests we don't support - pthread_tests = [t for t in pthread_tests if not t.startswith('pthread_sigmask')] return pthread_tests @@ -56,15 +54,15 @@ def get_pthread_tests(): 'test_pthread_atfork_3_2': 'fork() and multiple processes are not supported', 'test_pthread_atfork_4_1': 'fork() and multiple processes are not supported', 'test_pthread_kill_1_1': 'signals are not supported', - 'test_pthread_create_1_5': 'semaphores are not supported', + 'test_pthread_create_1_5': 'fork() and multiple processes are not supported', 'test_pthread_exit_6_1': 'lacking necessary mmap() support', 'test_pthread_spin_lock_1_1': 'signals are not supported', 'test_pthread_mutex_lock_5_1': 'signals are not supported', 'test_pthread_mutexattr_settype_2_1': 'interrupting pthread_mutex_lock wait via SIGALRM is not supported', 'test_pthread_spin_lock_3_1': 'signals are not supported', - 'test_pthread_create_14_1': 'signals are not supported', - 'test_pthread_detach_4_3': 'signals are not supported', - 'test_pthread_join_6_3': 'signals are not supported', + 'test_pthread_create_14_1': 'creates too many threads', + 'test_pthread_detach_4_3': 'creates too many threads', + 'test_pthread_join_6_3': 'creates too many threads', 'test_pthread_barrier_wait_3_2': 'signals are not supported', 'test_pthread_cond_broadcast_1_2': 'tries to create 10,0000 threads, then depends on fork()', } diff --git a/tests/test_signals.c b/tests/test_signals.c new file mode 100644 index 0000000000000..711493215ecb7 --- /dev/null +++ b/tests/test_signals.c @@ -0,0 +1,136 @@ +// Copyright 2021 The Emscripten Authors. All rights reserved. +// Emscripten is available under two separate licenses, the MIT license and the +// University of Illinois/NCSA Open Source License. Both these licenses can be +// found in the LICENSE file. + +#define _GNU_SOURCE // for sighandler_t +#include +#include +#include +#include +#include + +bool recieved1 = false; +bool recieved2 = false; + +void block_sigusr1() { + sigset_t old; + sigset_t set; + sigaddset(&set, SIGUSR1); + int rc = sigprocmask(SIG_BLOCK, &set, &old); + assert(rc == 0); + assert(!sigismember(&old, SIGUSR1)); +} + +void unblock_all() { + sigset_t old; + sigset_t set; + sigfillset(&set); + int rc = sigprocmask(SIG_UNBLOCK, &set, &old); + assert(rc == 0); +} + +void handler1(int sig) { + printf("handler1: %d\n", sig); + assert(sig == SIGUSR1); + recieved1 = true; +} + +void test_bad_signal() { + printf("test_bad_signal\n"); + sighandler_t old = signal(1024, handler1); + assert(old == SIG_ERR); +} + +void test_raise_sigusr1() { + printf("test_raise_sigusr1\n"); + sighandler_t old = signal(SIGUSR1, handler1); + assert(old != SIG_ERR); + + raise(SIGUSR1); + + int count = 0; + while (!recieved1) { + usleep(1000); + if (count++ == 1000) { + printf("handler1 not run\n"); + assert(false); + } + } +} + +void test_sigpenging() { + printf("test_sigpending\n"); + recieved1 = false; + + // Test that the pending set is empty + sigset_t pending; + sigpending(&pending); + assert(!sigismember(&pending, SIGUSR1)); + + block_sigusr1(); + + sighandler_t old_handler = signal(SIGUSR1, handler1); + assert(old_handler != SIG_ERR); + + raise(SIGUSR1); + + while (1) { + // Check that SIGUSR1 is now pending. + sigpending(&pending); + if (sigismember(&pending, SIGUSR1)) { + printf("is now pending\n"); + break; + } + printf("is not pending\n"); + } + + // Unlock the signal and then check that is recieved. + assert(!recieved1); + unblock_all(); + + int count = 0; + while (!recieved1) { + usleep(1000); + if (count++ == 1000) { + printf("handler1 not run\n"); + assert(false); + } + } + + // Signal should no longer be pending + sigpending(&pending); + assert(!sigismember(&pending, SIGUSR1)); +} + +void test_sigwaitinfo() { + recieved1 = false; + block_sigusr1(); + + raise(SIGUSR1); + + sighandler_t old_handler = signal(SIGUSR1, handler1); + assert(old_handler != SIG_ERR); + + sigset_t set; + siginfo_t info; + sigaddset(&set, SIGUSR1); + int rc = sigwaitinfo(&set, &info); + assert(rc == SIGUSR1); + + // Signal should no longer be pending + sigset_t pending; + sigpending(&pending); + assert(!sigismember(&pending, SIGUSR1)); + + unblock_all(); + assert(!recieved1); +} + +int main() { + test_bad_signal(); + test_raise_sigusr1(); + test_sigpenging(); + test_sigwaitinfo(); + return 0; +} diff --git a/tests/test_signals.out b/tests/test_signals.out new file mode 100644 index 0000000000000..ba3c0228cb9a9 --- /dev/null +++ b/tests/test_signals.out @@ -0,0 +1,6 @@ +test_bad_signal +test_raise_sigusr1 +handler1: 10 +test_sigpending +is now pending +handler1: 10 diff --git a/tools/deps_info.py b/tools/deps_info.py index 0d1e43dc64245..0db2d26bd38b7 100644 --- a/tools/deps_info.py +++ b/tools/deps_info.py @@ -51,6 +51,7 @@ from tools.settings import settings _deps_info = { + 'alarm': ['raise'], 'Mix_LoadWAV_RW': ['fileno'], 'SDL_CreateRGBSurface': ['malloc', 'free'], 'SDL_GL_GetProcAddress': ['malloc'], diff --git a/tools/system_libs.py b/tools/system_libs.py index 326fd8dec13e3..9c7bd30fd3450 100644 --- a/tools/system_libs.py +++ b/tools/system_libs.py @@ -845,16 +845,20 @@ def get_files(self): 'getitimer.c', 'killpg.c', 'setitimer.c', + 'sigorset.c', + 'sigandset.c', 'sigaddset.c', 'sigdelset.c', 'sigemptyset.c', 'sigfillset.c', 'sigismember.c', + 'siginterrupt.c', 'signal.c', 'sigprocmask.c', 'sigrtmax.c', 'sigrtmin.c', 'sigwait.c', + 'sigwaitinfo.c', ]) libc_files += files_in_path( @@ -863,6 +867,11 @@ def get_files(self): 'extras.c', 'wasi-helpers.c', 'emscripten_get_heap_size.c', + 'raise.c', + 'kill.c', + 'sigaction.c', + 'sigtimedwait.c', + 'pthread_sigmask.c', ]) libc_files += files_in_path(