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() int main()
{ {
sigset_t set, old_set; 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); sigemptyset(&set);
sigprocmask(SIG_BLOCK, &set, &old_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 init_signal_handlers PROTO((void));
static void keyboard_interrupt PROTO((ptr tc)); static void keyboard_interrupt PROTO((ptr tc));
static void (*register_modified_signal)(int);
ptr S_get_scheme_arg(tc, n) ptr tc; iptr n; { ptr S_get_scheme_arg(tc, n) ptr tc; iptr n; {
if (n <= asm_arg_reg_cnt) return REGARG(tc, 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); do_error(ERROR_NONCONTINUABLE_INTERRUPT,"","",Snil);
} }
void Sscheme_register_signal_registerer(void (*registerer)(int)) {
register_modified_signal = registerer;
}
#ifdef WIN32 #ifdef WIN32
ptr S_dequeue_scheme_signals(UNUSED ptr tc) { ptr S_dequeue_scheme_signals(UNUSED ptr tc) {
return Snil; 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() { static void init_signal_handlers() {
struct sigaction act; struct sigaction act;
if (register_modified_signal == NULL)
register_modified_signal = no_op_register;
sigemptyset(&act.sa_mask); sigemptyset(&act.sa_mask);
/* drop pending keyboard interrupts */ /* drop pending keyboard interrupts */
act.sa_flags = 0; act.sa_flags = 0;
act.sa_handler = SIG_IGN; act.sa_handler = SIG_IGN;
sigaction(SIGINT, &act, (struct sigaction *)0); SIGACTION(SIGINT, &act, (struct sigaction *)0);
/* ignore broken pipe signals */ /* ignore broken pipe signals */
act.sa_flags = 0; act.sa_flags = 0;
act.sa_handler = SIG_IGN; 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 */ /* set up to catch SIGINT w/no system call restart */
#ifdef SA_INTERRUPT #ifdef SA_INTERRUPT
@ -748,7 +762,7 @@ static void init_signal_handlers() {
act.sa_flags = SA_SIGINFO; act.sa_flags = SA_SIGINFO;
#endif /* SA_INTERRUPT */ #endif /* SA_INTERRUPT */
act.sa_sigaction = handle_signal; act.sa_sigaction = handle_signal;
sigaction(SIGINT, &act, (struct sigaction *)0); SIGACTION(SIGINT, &act, (struct sigaction *)0);
#ifdef BSDI #ifdef BSDI
siginterrupt(SIGINT, 1); siginterrupt(SIGINT, 1);
#endif #endif
@ -760,14 +774,14 @@ static void init_signal_handlers() {
act.sa_flags |= SA_RESTART; act.sa_flags |= SA_RESTART;
#endif /* SA_RESTART */ #endif /* SA_RESTART */
#ifdef SIGQUIT #ifdef SIGQUIT
sigaction(SIGQUIT, &act, (struct sigaction *)0); SIGACTION(SIGQUIT, &act, (struct sigaction *)0);
#endif /* SIGQUIT */ #endif /* SIGQUIT */
sigaction(SIGILL, &act, (struct sigaction *)0); SIGACTION(SIGILL, &act, (struct sigaction *)0);
sigaction(SIGFPE, &act, (struct sigaction *)0); SIGACTION(SIGFPE, &act, (struct sigaction *)0);
#ifdef SIGBUS #ifdef SIGBUS
sigaction(SIGBUS, &act, (struct sigaction *)0); SIGACTION(SIGBUS, &act, (struct sigaction *)0);
#endif /* SIGBUS */ #endif /* SIGBUS */
sigaction(SIGSEGV, &act, (struct sigaction *)0); SIGACTION(SIGSEGV, &act, (struct sigaction *)0);
} }
#endif /* WIN32 */ #endif /* WIN32 */

View File

@ -445,6 +445,7 @@
(export "int" "Sscheme_script" "(const char *, int, const char *[])") (export "int" "Sscheme_script" "(const char *, int, const char *[])")
(export "int" "Sscheme_program" "(const char *, int, const char *[])") (export "int" "Sscheme_program" "(const char *, int, const char *[])")
(export "void" "Sscheme_deinit" "(void)") (export "void" "Sscheme_deinit" "(void)")
(export "void" "Sscheme_register_signal_registerer" "(void (*f)(int))")
(when-feature pthreads (when-feature pthreads
(nl) (comment "Thread support.") (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 Called by GC when it has to give up, maybe due to running out of memory
during a collection. */ 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); GC2_EXTERN void GC_dump(void);
/* /*
Dumps memory state info to stderr. */ 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; GC_Out_Of_Memory_Proc GC_out_of_memory;
void (*GC_report_out_of_memory)(void); 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) 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 # ifdef NEED_SIGSTACK
act.sa_flags |= SA_ONSTACK; act.sa_flags |= SA_ONSTACK;
# endif # endif
if (GC_report_signal_handle_modify)
GC_report_signal_handle_modify(USE_SIGACTON_SIGNAL_KIND);
sigaction(USE_SIGACTON_SIGNAL_KIND, &act, &oact); sigaction(USE_SIGACTON_SIGNAL_KIND, &act, &oact);
# ifdef USE_ANOTHER_SIGACTON_SIGNAL_KIND # 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); sigaction(USE_ANOTHER_SIGACTON_SIGNAL_KIND, &act, &oact);
# endif # 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 void scheme_set_stack_base(void *base, int no_auto_statics) XFORM_SKIP_PROC
{ {
#ifdef MZ_PRECISE_GC #ifdef MZ_PRECISE_GC
GC_report_signal_handle_modify = rktio_will_modify_os_signal_handler;
GC_init_type_tags(_scheme_last_type_, GC_init_type_tags(_scheme_last_type_,
scheme_pair_type, scheme_mutable_pair_type, scheme_weak_box_type, scheme_pair_type, scheme_mutable_pair_type, scheme_weak_box_type,
scheme_ephemeron_type, scheme_rt_weak_array, 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); sigemptyset(&sa.sa_mask);
sa.sa_flags = 0; sa.sa_flags = 0;
sa.sa_handler = (proc ? proc : SIG_IGN); sa.sa_handler = (proc ? proc : SIG_IGN);
rktio_will_modify_os_signal_handler(sig_id);
sigaction(sig_id, &sa, NULL); sigaction(sig_id, &sa, NULL);
#endif #endif
} }
@ -1655,6 +1657,7 @@ static void install_w_xor_x_handler()
sigaddset(&act.sa_mask, SIGINT); sigaddset(&act.sa_mask, SIGINT);
sigaddset(&act.sa_mask, SIGCHLD); sigaddset(&act.sa_mask, SIGCHLD);
act.sa_flags = SA_SIGINFO; act.sa_flags = SA_SIGINFO;
rktio_will_modify_os_signal_handler(SIG_W_XOR_X);
sigaction(SIG_W_XOR_X, &act, &oact); sigaction(SIG_W_XOR_X, &act, &oact);
previous_fault_handler = oact.sa_sigaction; 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); rktio_set_dll_procs(ba->dll_open, ba->dll_find_object, ba->dll_close);
#endif #endif
Sscheme_register_signal_registerer(rktio_will_modify_os_signal_handler);
Sscheme_init(NULL); Sscheme_init(NULL);
if ((ba->argc == 4) && !strcmp(ba->argv[0], "--cross-server")) 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); RKTIO_EXTERN void rktio_install_os_signal_handler(rktio_t *rktio);
/* Installs OS-level handlers for SIGINT, SIGTERM, and SIGHUP (or /* 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 the signal for reporting via `rktio_poll_os_signal`. Only one
`rktio` can be registered this way at a time. This function must `rktio` can be registered this way at a time. This function must
not be called in two threads at the same time. */ not be called in two threads at the same time. */
@ -1016,6 +1016,17 @@ enum {
RKTIO_NUM_OS_SIGNALS 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 */ /* Time and date */

View File

@ -416,9 +416,11 @@ char *rktio_strndup(char *s, intptr_t len);
#ifdef RKTIO_SYSTEM_UNIX #ifdef RKTIO_SYSTEM_UNIX
void rktio_set_signal_handler(int sig_id, void (*proc)(int)); void rktio_set_signal_handler(int sig_id, void (*proc)(int));
void rktio_restore_modified_signal_handlers();
#endif #endif
void rktio_forget_os_signal_handler(rktio_t *rktio); void rktio_forget_os_signal_handler(rktio_t *rktio);
#ifdef RKTIO_SYSTEM_WINDOWS #ifdef RKTIO_SYSTEM_WINDOWS
int rktio_system_time_is_dst(SYSTEMTIME *st, TIME_ZONE_INFORMATION *_tz); int rktio_system_time_is_dst(SYSTEMTIME *st, TIME_ZONE_INFORMATION *_tz);
#endif #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_close_fds_after_fork(close_after_len, 0, 1, 2);
} }
rktio_restore_modified_signal_handlers();
/* Set real CWD: */ /* Set real CWD: */
if (!rktio_set_current_directory(rktio, current_directory)) { if (!rktio_set_current_directory(rktio, current_directory)) {
fprintf(stderr, "racket: chdir failed to: %s\n", 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); sigemptyset(&sa.sa_mask);
sa.sa_flags = 0; sa.sa_flags = 0;
sa.sa_handler = proc; sa.sa_handler = proc;
rktio_will_modify_os_signal_handler(sig_id);
sigaction(sig_id, &sa, NULL); sigaction(sig_id, &sa, NULL);
} }
#endif #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