Unix: restore all signal handlers after creating a subprocess fork

For each signal handler that is changed, save its state, and restore
the state in a child process after a `fork` and before an `execve`.
Also save and restore the signal mask.

This change requires cooperation from various subsystems, which often
takes the form of a callback registered with the subsystem to be
called before adjusts a signal handler.

Closes #3609
This commit is contained in:
Matthew Flatt 2021-02-10 14:31:53 -07:00
parent 2ca6f7a5d6
commit 2cbaeb7865
12 changed files with 115 additions and 9 deletions

View File

@ -7,6 +7,16 @@
int main()
{
sigset_t set, old_set;
struct sigaction sa;
int i;
/* SIGPROF tends to be near the end of the range of signal IDs */
for (i = 0; i < SIGPROF; i++) {
sigaction(i, NULL, &sa);
if (sa.sa_handler != SIG_DFL)
return 1;
}
sigemptyset(&set);
sigprocmask(SIG_BLOCK, &set, &old_set);

View File

@ -25,6 +25,8 @@ static void handle_call_error PROTO((ptr tc, iptr type, ptr x));
static void init_signal_handlers PROTO((void));
static void keyboard_interrupt PROTO((ptr tc));
static void (*register_modified_signal)(int);
ptr S_get_scheme_arg(tc, n) ptr tc; iptr n; {
if (n <= asm_arg_reg_cnt) return REGARG(tc, n);
@ -533,6 +535,10 @@ void S_noncontinuable_interrupt() {
do_error(ERROR_NONCONTINUABLE_INTERRUPT,"","",Snil);
}
void Sscheme_register_signal_registerer(void (*registerer)(int)) {
register_modified_signal = registerer;
}
#ifdef WIN32
ptr S_dequeue_scheme_signals(UNUSED ptr tc) {
return Snil;
@ -726,20 +732,28 @@ static void handle_signal(INT sig, UNUSED siginfo_t *si, UNUSED void *data) {
}
}
static void no_op_register(UNUSED int sigid) {
}
#define SIGACTION(id, act_p, old_p) (register_modified_signal(id), sigaction(id, act_p, old_p))
static void init_signal_handlers() {
struct sigaction act;
if (register_modified_signal == NULL)
register_modified_signal = no_op_register;
sigemptyset(&act.sa_mask);
/* drop pending keyboard interrupts */
act.sa_flags = 0;
act.sa_handler = SIG_IGN;
sigaction(SIGINT, &act, (struct sigaction *)0);
SIGACTION(SIGINT, &act, (struct sigaction *)0);
/* ignore broken pipe signals */
act.sa_flags = 0;
act.sa_handler = SIG_IGN;
sigaction(SIGPIPE, &act, (struct sigaction *)0);
SIGACTION(SIGPIPE, &act, (struct sigaction *)0);
/* set up to catch SIGINT w/no system call restart */
#ifdef SA_INTERRUPT
@ -748,7 +762,7 @@ static void init_signal_handlers() {
act.sa_flags = SA_SIGINFO;
#endif /* SA_INTERRUPT */
act.sa_sigaction = handle_signal;
sigaction(SIGINT, &act, (struct sigaction *)0);
SIGACTION(SIGINT, &act, (struct sigaction *)0);
#ifdef BSDI
siginterrupt(SIGINT, 1);
#endif
@ -760,14 +774,14 @@ static void init_signal_handlers() {
act.sa_flags |= SA_RESTART;
#endif /* SA_RESTART */
#ifdef SIGQUIT
sigaction(SIGQUIT, &act, (struct sigaction *)0);
SIGACTION(SIGQUIT, &act, (struct sigaction *)0);
#endif /* SIGQUIT */
sigaction(SIGILL, &act, (struct sigaction *)0);
sigaction(SIGFPE, &act, (struct sigaction *)0);
SIGACTION(SIGILL, &act, (struct sigaction *)0);
SIGACTION(SIGFPE, &act, (struct sigaction *)0);
#ifdef SIGBUS
sigaction(SIGBUS, &act, (struct sigaction *)0);
SIGACTION(SIGBUS, &act, (struct sigaction *)0);
#endif /* SIGBUS */
sigaction(SIGSEGV, &act, (struct sigaction *)0);
SIGACTION(SIGSEGV, &act, (struct sigaction *)0);
}
#endif /* WIN32 */

View File

@ -445,6 +445,7 @@
(export "int" "Sscheme_script" "(const char *, int, const char *[])")
(export "int" "Sscheme_program" "(const char *, int, const char *[])")
(export "void" "Sscheme_deinit" "(void)")
(export "void" "Sscheme_register_signal_registerer" "(void (*f)(int))")
(when-feature pthreads
(nl) (comment "Thread support.")

View File

@ -139,6 +139,10 @@ GC2_EXTERN void (*GC_report_out_of_memory)(void);
Called by GC when it has to give up, maybe due to running out of memory
during a collection. */
GC2_EXTERN void (*GC_report_signal_handle_modify)(int);
/*
Called by GC just before it changes an OS signal handler. */
GC2_EXTERN void GC_dump(void);
/*
Dumps memory state info to stderr. */

View File

@ -185,6 +185,7 @@ extern double scheme_get_inexact_milliseconds(void);
GC_Out_Of_Memory_Proc GC_out_of_memory;
void (*GC_report_out_of_memory)(void);
void (*GC_report_signal_handle_modify)(int);
GC_Out_Of_Memory_Proc GC_get_out_of_memory(void)
{

View File

@ -247,8 +247,12 @@ static void initialize_signal_handler(GCTYPE *gc)
# ifdef NEED_SIGSTACK
act.sa_flags |= SA_ONSTACK;
# endif
if (GC_report_signal_handle_modify)
GC_report_signal_handle_modify(USE_SIGACTON_SIGNAL_KIND);
sigaction(USE_SIGACTON_SIGNAL_KIND, &act, &oact);
# ifdef USE_ANOTHER_SIGACTON_SIGNAL_KIND
if (GC_report_signal_handle_modify)
GC_report_signal_handle_modify(USE_ANOTHER_SIGACTON_SIGNAL_KIND);
sigaction(USE_ANOTHER_SIGACTON_SIGNAL_KIND, &act, &oact);
# endif
}

View File

@ -107,6 +107,7 @@ extern MZGC_DLLIMPORT void GC_init();
void scheme_set_stack_base(void *base, int no_auto_statics) XFORM_SKIP_PROC
{
#ifdef MZ_PRECISE_GC
GC_report_signal_handle_modify = rktio_will_modify_os_signal_handler;
GC_init_type_tags(_scheme_last_type_,
scheme_pair_type, scheme_mutable_pair_type, scheme_weak_box_type,
scheme_ephemeron_type, scheme_rt_weak_array,
@ -464,6 +465,7 @@ void scheme_set_signal_handler(int sig_id, Scheme_Signal_Handler_Proc proc) XFOR
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sa.sa_handler = (proc ? proc : SIG_IGN);
rktio_will_modify_os_signal_handler(sig_id);
sigaction(sig_id, &sa, NULL);
#endif
}
@ -1655,6 +1657,7 @@ static void install_w_xor_x_handler()
sigaddset(&act.sa_mask, SIGINT);
sigaddset(&act.sa_mask, SIGCHLD);
act.sa_flags = SA_SIGINFO;
rktio_will_modify_os_signal_handler(SIG_W_XOR_X);
sigaction(SIG_W_XOR_X, &act, &oact);
previous_fault_handler = oact.sa_sigaction;
}

View File

@ -109,6 +109,8 @@ void racket_boot(racket_boot_arguments_t *ba)
rktio_set_dll_procs(ba->dll_open, ba->dll_find_object, ba->dll_close);
#endif
Sscheme_register_signal_registerer(rktio_will_modify_os_signal_handler);
Sscheme_init(NULL);
if ((ba->argc == 4) && !strcmp(ba->argv[0], "--cross-server"))

View File

@ -1001,7 +1001,7 @@ RKTIO_EXTERN void rktio_flush_signals_received(rktio_t *rktio);
RKTIO_EXTERN void rktio_install_os_signal_handler(rktio_t *rktio);
/* Installs OS-level handlers for SIGINT, SIGTERM, and SIGHUP (or
Ctl-C on Windows) to signal the handle of `rktio` and also record
Ctl-C on Windows) to signal the handle of `rktio` and also records
the signal for reporting via `rktio_poll_os_signal`. Only one
`rktio` can be registered this way at a time. This function must
not be called in two threads at the same time. */
@ -1016,6 +1016,17 @@ enum {
RKTIO_NUM_OS_SIGNALS
};
RKTIO_EXTERN void rktio_will_modify_os_signal_handler(int sig_id);
/* Registers with rktio that an operating-system signal handler is
about to be modified within the process but outside of rktio, where
`sig_id` is a signal identifier --- such as SIGINT or SIGTERM. This
notification allows rktio to record the current signal disposition
so that it can be restored after forking a new Unix process.
Signal registrations should happen only before multiple threads use
rktio, and registration of the signal can happen before any
`rktio_init` call. On the first `rktio_will_modify_os_signal_handler`
call, the signal mask is also recorded to be restored in a fork. */
/*************************************************/
/* Time and date */

View File

@ -416,9 +416,11 @@ char *rktio_strndup(char *s, intptr_t len);
#ifdef RKTIO_SYSTEM_UNIX
void rktio_set_signal_handler(int sig_id, void (*proc)(int));
void rktio_restore_modified_signal_handlers();
#endif
void rktio_forget_os_signal_handler(rktio_t *rktio);
#ifdef RKTIO_SYSTEM_WINDOWS
int rktio_system_time_is_dst(SYSTEMTIME *st, TIME_ZONE_INFORMATION *_tz);
#endif

View File

@ -1526,6 +1526,8 @@ rktio_process_result_t *rktio_process(rktio_t *rktio,
rktio_close_fds_after_fork(close_after_len, 0, 1, 2);
}
rktio_restore_modified_signal_handlers();
/* Set real CWD: */
if (!rktio_set_current_directory(rktio, current_directory)) {
fprintf(stderr, "racket: chdir failed to: %s\n", current_directory);

View File

@ -103,6 +103,58 @@ void rktio_set_signal_handler(int sig_id, void (*proc)(int))
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sa.sa_handler = proc;
rktio_will_modify_os_signal_handler(sig_id);
sigaction(sig_id, &sa, NULL);
}
#endif
/*========================================================================*/
/* Signal handler original state */
/*========================================================================*/
typedef struct signal_handler_saved_disposition {
int sig_id;
struct signal_handler_saved_disposition *next;
#if defined(RKTIO_SYSTEM_UNIX)
struct sigaction sa;
#endif
} signal_handler_saved_disposition;
static signal_handler_saved_disposition *saved_dispositions;
static sigset_t initial_procmask;
void rktio_will_modify_os_signal_handler(int sig_id) {
signal_handler_saved_disposition *saved;
#ifdef RKTIO_SYSTEM_UNIX
if (saved_dispositions == NULL)
sigprocmask(SIG_SETMASK, NULL, &initial_procmask);
#endif
for (saved = saved_dispositions; saved; saved = saved->next)
if (saved->sig_id == sig_id)
return;
saved = malloc(sizeof(signal_handler_saved_disposition));
saved->next = saved_dispositions;
saved->sig_id = sig_id;
saved_dispositions = saved;
#if defined(RKTIO_SYSTEM_UNIX)
sigaction(sig_id, NULL, &saved->sa);
#endif
}
#ifdef RKTIO_SYSTEM_UNIX
/* called in a child thread after `fork */
void rktio_restore_modified_signal_handlers() {
if (saved_dispositions) {
signal_handler_saved_disposition *saved;
for (saved = saved_dispositions; saved; saved = saved->next)
sigaction(saved->sig_id, &saved->sa, NULL);
sigprocmask(SIG_SETMASK, &initial_procmask, NULL);
}
}
#endif