From 6674a504c1d4d2aa6a6d7d1c185f52e29048b92e Mon Sep 17 00:00:00 2001 From: daveads Date: Wed, 19 Nov 2025 01:54:09 +0000 Subject: [PATCH 1/2] traps --- builtin/trap_osh.py | 51 +++++++++++++++++++++++++++++++++++++++++++- frontend/flag_def.py | 1 + 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/builtin/trap_osh.py b/builtin/trap_osh.py index 8d9af9578c..a31eb34cd3 100644 --- a/builtin/trap_osh.py +++ b/builtin/trap_osh.py @@ -1,7 +1,7 @@ #!/usr/bin/env python2 from __future__ import print_function -from signal import SIG_DFL, SIGINT, SIGWINCH +from signal import SIG_DFL, SIG_IGN, SIGINT, SIGWINCH from _devbuild.gen import arg_types from _devbuild.gen.runtime_asdl import cmd_value @@ -49,6 +49,7 @@ def __init__(self, signal_safe): self.signal_safe = signal_safe self.hooks = {} # type: Dict[str, command_t] self.traps = {} # type: Dict[int, command_t] + self.ignored_sigs = {} # type: Dict[int, bool] # Signals set to SIG_IGN def ClearForSubProgram(self, inherit_errtrace): # type: (bool) -> None @@ -62,6 +63,7 @@ def ClearForSubProgram(self, inherit_errtrace): self.hooks['ERR'] = hook_err self.traps.clear() + self.ignored_sigs.clear() def GetHook(self, hook_name): # type: (str) -> command_t @@ -93,6 +95,7 @@ def _RemoveUserTrap(self, sig_num): # type: (int) -> None mylib.dict_erase(self.traps, sig_num) + mylib.dict_erase(self.ignored_sigs, sig_num) if sig_num == SIGINT: self.signal_safe.SetSigIntTrapped(False) @@ -110,6 +113,20 @@ def _RemoveUserTrap(self, sig_num): # shells do. iolib.sigaction(sig_num, SIG_DFL) + def _IgnoreSignal(self, sig_num): + # type: (int) -> None + """Set a signal to be ignored (SIG_IGN).""" + mylib.dict_erase(self.traps, sig_num) + self.ignored_sigs[sig_num] = True + + if sig_num == SIGINT: + # Don't disturb SIGINT handling + self.signal_safe.SetSigIntTrapped(False) + elif sig_num == SIGWINCH: + self.signal_safe.SetSigWinchCode(iolib.UNTRAPPED_SIGWINCH) + else: + iolib.sigaction(sig_num, SIG_IGN) + def AddItem(self, parsed_id, handler): # type: (str, command_t) -> None """Add trap or hook, parsed to EXIT or INT (not 0 or SIGINT)""" @@ -346,6 +363,31 @@ def _RemoveTheRest(self, arg_r, allow_legacy=True): self.trap_state.RemoveItem(parsed_id) arg_r.Next() + def _IgnoreTheRest(self, arg_r): + # type: (args.Reader) -> int + """Ignore (set to SIG_IGN) all remaining signal arguments""" + while not arg_r.AtEnd(): + arg_str, arg_loc = arg_r.Peek2() + parsed_id = ParseSignalOrHook(arg_str, arg_loc, allow_legacy=True) + + if parsed_id in _HOOK_NAMES: + self.errfmt.Print_( + "trap: can't ignore hook %r" % arg_str, + blame_loc=arg_loc) + return 2 + + sig_num = signal_def.GetNumber(parsed_id) + assert sig_num is not signal_def.NO_SIGNAL + + if parsed_id == 'STOP' or parsed_id == 'KILL': + self.errfmt.Print_("Signal %r can't be handled" % arg_str, + blame_loc=arg_loc) + return 2 + + self.trap_state._IgnoreSignal(sig_num) + arg_r.Next() + return 0 + def Run(self, cmd_val): # type: (cmd_value.Argv) -> int attrs, arg_r = flag_util.ParseCmdVal('trap', @@ -357,6 +399,9 @@ def Run(self, cmd_val): cmd_frag = typed_args.RequiredBlockAsFrag(cmd_val) return self._AddTheRest(arg_r, cmd_frag, allow_legacy=False) + if arg.ignore: # trap --ignore + return self._IgnoreTheRest(arg_r) + if arg.remove: # trap --remove self._RemoveTheRest(arg_r, allow_legacy=False) return 0 @@ -396,6 +441,10 @@ def Run(self, cmd_val): arg_r.Next() + # If first arg is empty string '', ignore the specified signals + if first_arg == '': + return self._IgnoreTheRest(arg_r) + # Legacy behavior for only one arg: 'trap SIGNAL' removes the handler if arg_r.AtEnd(): parsed_id = ParseSignalOrHook(first_arg, first_loc) diff --git a/frontend/flag_def.py b/frontend/flag_def.py index 864450dc9d..6e6678d012 100644 --- a/frontend/flag_def.py +++ b/frontend/flag_def.py @@ -203,6 +203,7 @@ TRAP_SPEC.ShortFlag('-l') TRAP_SPEC.LongFlag('--add') TRAP_SPEC.LongFlag('--remove') +TRAP_SPEC.LongFlag('--ignore') # YSH: ignore signals (same as trap '') KILL_SPEC = FlagSpec('kill') KILL_SPEC.ShortFlag('-l', args.Bool) From 04ce04514a19ebf30e6683f49dcb64a61a04d513 Mon Sep 17 00:00:00 2001 From: daveads Date: Wed, 19 Nov 2025 02:13:21 +0000 Subject: [PATCH 2/2] test --- builtin/trap_osh.py | 7 ++++++ spec/builtin-trap.test.sh | 46 +++++++++++++++++++++++++++++++++++ spec/ysh-builtin-trap.test.sh | 26 ++++++++++++++++++++ 3 files changed, 79 insertions(+) diff --git a/builtin/trap_osh.py b/builtin/trap_osh.py index a31eb34cd3..7d83002112 100644 --- a/builtin/trap_osh.py +++ b/builtin/trap_osh.py @@ -312,6 +312,13 @@ def _PrintState(self): # Print in order of signal number n = signal_def.MaxSigNumber() + 1 for sig_num in xrange(n): + # Check for explicitly ignored signals + if sig_num in self.trap_state.ignored_sigs: + sig_name = signal_def.GetName(sig_num) + assert sig_name is not None + print("trap -- '' %s" % sig_name) + continue + handler = self.trap_state.GetTrap(sig_num) if handler is None: continue diff --git a/spec/builtin-trap.test.sh b/spec/builtin-trap.test.sh index 296ed6479d..f1b894d935 100644 --- a/spec/builtin-trap.test.sh +++ b/spec/builtin-trap.test.sh @@ -561,3 +561,49 @@ ok07 trap-exit failure ## END + +#### trap '' sets handler to empty string (SIG_IGN) + +trap '' USR1 +trap + +## STDOUT: +trap -- '' SIGUSR1 +## END + +#### trap '' with multiple signals + +trap '' USR1 USR2 +trap + +## STDOUT: +trap -- '' SIGUSR1 +trap -- '' SIGUSR2 +## END + +#### trap '' rejects hooks + +trap '' EXIT +echo should not get here + +## STDOUT: +## END +## status: 2 + +#### trap '' rejects STOP signal + +trap '' STOP +echo should not get here + +## STDOUT: +## END +## status: 2 + +#### trap '' rejects KILL signal + +trap '' KILL +echo should not get here + +## STDOUT: +## END +## status: 2 diff --git a/spec/ysh-builtin-trap.test.sh b/spec/ysh-builtin-trap.test.sh index 0c96f894a0..7e4f71fc38 100644 --- a/spec/ysh-builtin-trap.test.sh +++ b/spec/ysh-builtin-trap.test.sh @@ -44,3 +44,29 @@ register ## STDOUT: x = global ## END + +#### trap --ignore INT USR1 + +trap --ignore INT USR1 +trap -p + +## STDOUT: +trap -- '' SIGINT +trap -- '' SIGUSR1 +## END + +#### trap --ignore rejects hooks + +trap --ignore EXIT + +## STDOUT: +## END +## status: 2 + +#### trap --ignore rejects STOP + +trap --ignore STOP + +## STDOUT: +## END +## status: 2