From ca4fa3865a730857b385e6e34af67db3471e0089 Mon Sep 17 00:00:00 2001 From: Steve Bennett Date: Wed, 21 Aug 2019 11:27:56 +1000 Subject: [PATCH] signal: Add 'signal block' support This allows a signal to be blocked by setting it's handler to SIG_IGN Can be used to block SIGPIPE for exec Signed-off-by: Steve Bennett --- jim-signal.c | 46 ++++++++++++++++++++++++++++++---------------- jim_tcl.txt | 11 ++++++++++- tests/exec2.test | 32 +++++++++++++++++++++++++------- 3 files changed, 65 insertions(+), 24 deletions(-) diff --git a/jim-signal.c b/jim-signal.c index cec541a..7d02e68 100644 --- a/jim-signal.c +++ b/jim-signal.c @@ -22,7 +22,7 @@ #endif static jim_wide *sigloc; -static jim_wide sigsblocked; +static jim_wide sigsignored; static struct sigaction *sa_old; static struct { int status; @@ -34,16 +34,16 @@ static struct { static void signal_handler(int sig) { - /* We just remember which signals occurred. Jim_Eval() will - * notice this as soon as it can and throw an error + /* Remember which signals occurred and store in *sigloc. + * Jim_Eval() will notice this as soon as it can and throw an error */ *sigloc |= sig_to_bit(sig); } static void signal_ignorer(int sig) { - /* We just remember which signals occurred */ - sigsblocked |= sig_to_bit(sig); + /* Remember which signals occurred for access by 'signal check' */ + sigsignored |= sig_to_bit(sig); } static void signal_init_names(void) @@ -169,6 +169,7 @@ static int find_signal_by_name(Jim_Interp *interp, const char *name) #define SIGNAL_ACTION_HANDLE 1 #define SIGNAL_ACTION_IGNORE -1 +#define SIGNAL_ACTION_BLOCK -2 #define SIGNAL_ACTION_DEFAULT 0 static int do_signal_cmd(Jim_Interp *interp, int action, int argc, Jim_Obj *const *argv) @@ -194,9 +195,13 @@ static int do_signal_cmd(Jim_Interp *interp, int action, int argc, Jim_Obj *cons if (action == SIGNAL_ACTION_HANDLE) { sa.sa_handler = signal_handler; } - else { + else if (action == SIGNAL_ACTION_IGNORE) { sa.sa_handler = signal_ignorer; } + else { + /* SIGNAL_ACTION_BLOCK */ + sa.sa_handler = SIG_IGN; + } } /* Iterate through the provided signals */ @@ -209,6 +214,7 @@ static int do_signal_cmd(Jim_Interp *interp, int action, int argc, Jim_Obj *cons if (action != siginfo[sig].status) { /* Need to change the action for this signal */ switch (action) { + case SIGNAL_ACTION_BLOCK: case SIGNAL_ACTION_HANDLE: case SIGNAL_ACTION_IGNORE: if (siginfo[sig].status == SIGNAL_ACTION_DEFAULT) { @@ -246,6 +252,11 @@ static int signal_cmd_ignore(Jim_Interp *interp, int argc, Jim_Obj *const *argv) return do_signal_cmd(interp, SIGNAL_ACTION_IGNORE, argc, argv); } +static int signal_cmd_block(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + return do_signal_cmd(interp, SIGNAL_ACTION_BLOCK, argc, argv); +} + static int signal_cmd_default(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { return do_signal_cmd(interp, SIGNAL_ACTION_DEFAULT, argc, argv); @@ -269,7 +280,7 @@ static int signal_cmd_check(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { int clear = 0; jim_wide mask = 0; - jim_wide blocked; + jim_wide ignored; if (argc > 0 && Jim_CompareStringImmediate(interp, argv[0], "-clear")) { clear++; @@ -292,17 +303,13 @@ static int signal_cmd_check(Jim_Interp *interp, int argc, Jim_Obj *const *argv) mask = ~mask; } - if ((sigsblocked & mask) == 0) { - /* No matching signals, so empty result and nothing to do */ - return JIM_OK; - } /* Be careful we don't have a race condition where signals are cleared but not returned */ - blocked = sigsblocked & mask; + ignored = sigsignored & mask; if (clear) { - sigsblocked &= ~blocked; + sigsignored &= ~ignored; } /* Set the result */ - signal_set_sigmask_result(interp, blocked); + signal_set_sigmask_result(interp, ignored); return JIM_OK; } @@ -316,9 +323,9 @@ static int signal_cmd_throw(Jim_Interp *interp, int argc, Jim_Obj *const *argv) } } - /* If the signal is ignored (blocked) ... */ + /* If the signal is ignored ... */ if (siginfo[sig].status == SIGNAL_ACTION_IGNORE) { - sigsblocked |= sig_to_bit(sig); + sigsignored |= sig_to_bit(sig); return JIM_OK; } @@ -370,6 +377,13 @@ static const jim_subcmd_type signal_command_table[] = { -1, /* Description: Lists ignored signals, or adds to ignored signals */ }, + { "block", + "?signals ...?", + signal_cmd_block, + 0, + -1, + /* Description: Lists blocked signals, or adds to blocked signals */ + }, { "default", "?signals ...?", signal_cmd_default, diff --git a/jim_tcl.txt b/jim_tcl.txt index 247ad7e..352071e 100644 --- a/jim_tcl.txt +++ b/jim_tcl.txt @@ -57,6 +57,7 @@ Changes since 0.78 1. Add `file mtimeus` for high resolution file timestamps 2. `aio` now supports datagram Unix-Domain sockets 3. Add support for `aio lock -wait` +4. Add `signal block` to prevent delivery of signals Changes between 0.77 and 0.78 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -3798,8 +3799,16 @@ Commands which return a list of signal names do so using the canonical form: `signal check` to determine which signals have occurred but been ignored. ++*signal block* ?'signals \...'?+:: + If no signals are given, returns a lists all signals which are currently + being blocked. + If signals are specified, these are added to the list of signals + currently being blocked. These signals are not delivered to the process. + This can be useful for signals such as +SIGPIPE+, especially in conjunction + with `exec` as child processes inherit the parent's signal disposition. + +*signal default* ?'signals \...'?+:: - If no signals are given, returns a lists all signals which are currently have + If no signals are given, returns a lists all signals which currently have the default behaviour. If signals are specified, these are added to the list of signals which have the default behaviour. diff --git a/tests/exec2.test b/tests/exec2.test index 08f3d11..b4b42cc 100644 --- a/tests/exec2.test +++ b/tests/exec2.test @@ -5,12 +5,15 @@ source [file dirname [info script]]/testing.tcl needs cmd exec +foreach i {pipe signal wait} { + testConstraint $i [expr {[info commands $i] ne ""}] +} # Some Windows platforms (e.g. AppVeyor) produce ENOSPC rather than killing # the child with SIGPIPE). So turn off this test for that platform -if {[info commands pipe] ne "" && [env MSYSTEM ""] ne "MINGW32"} { - testConstraint pipe 1 +if {[info exists env(MSYSTEM)] && $env(MSYSTEM) eq "MINGW32"} { + testConstraint nomingw32 0 } else { - testConstraint pipe 0 + testConstraint nomingw32 1 } set d \" @@ -58,7 +61,7 @@ test exec2-3.1 "close pipeline return value" { list $rc $msg $status $exitcode } {1 {child process exited abnormally} CHILDSTATUS 1} -test exec2-3.2 "close pipeline return value" -constraints pipe -body { +test exec2-3.2 "close pipeline return value" -constraints {pipe nomingw32} -body { # Create a pipe and immediately close the read end lassign [pipe] r w close $r @@ -71,7 +74,23 @@ test exec2-3.2 "close pipeline return value" -constraints pipe -body { list $rc $msg $status $exitcode } -match glob -result {1 {child killed*} CHILDKILLED SIGPIPE} -test exec2-3.4 "wait for background task" { +test exec2-3.3 "close pipeline with SIGPIPE blocked" -constraints {pipe signal nomingw32} -body { + # Create a pipe and immediately close the read end + lassign [pipe] r w + close $r + signal block SIGPIPE + # Write more than 64KB which is maximum size of the pipe buffers + # on all systems we have seen + set bigstring [string repeat a 100000] + set f [open [list |cat << $bigstring >$@w 2>/dev/null]] + set rc [catch {close $f} msg opts] + lassign [dict get $opts -errorcode] status pid exitcode + list $rc $msg $status $exitcode +} -match glob -result {1 {child process exited*} CHILDSTATUS 1} -cleanup { + signal default SIGPIPE +} + +test exec2-3.4 "wait for background task" -constraints wait -body { set pid [exec sleep 0.1 &] lassign [wait $pid] status newpid exitcode if {$pid != $newpid} { @@ -79,7 +98,6 @@ test exec2-3.4 "wait for background task" { } else { list $status $exitcode } -} {CHILDSTATUS 0} - +} -result {CHILDSTATUS 0} testreport -- 2.11.4.GIT