From 15abd43f015a1a04118a4c057d4ff66350e458d5 Mon Sep 17 00:00:00 2001 From: Jim Riggs Date: Tue, 12 May 2020 21:57:47 -0500 Subject: [PATCH] Allow usage of signal names - Adds a mapping of signal numbers to signal names based on the "most common" (subjective) signals. - This allows the same config/script to be used on different platforms, for example: -r TERM:QUIT (15:3 on Linux vs. 25:20 on OS X). - Each entry is wrapped in a #ifdef SIG/#endif pair in an attempt to make this implementation as portable as possible. - Names can be specified with or without the SIG prefix, and numbers and names can be used in any combination: -r 15:3, -r TERM:QUIT, -r SIGTERM:3, -r TERM:SIGQUIT, etc. - Corresponding signum_to_signame() and signame_to_signum() functions were added. - A new -l/--list option shows the mapping in use on the current system/platform/OS. - Debug output has been updated to include the signal name along with the number. Fixes #87. --- README.md | 6 +- dumb-init.c | 216 +++++++++++++++++++++++++++++++--- tests/cli_test.py | 20 ++-- tests/proxies_signals_test.py | 18 +++ tests/tty_test.py | 8 +- 5 files changed, 242 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 2899c8d..05e6b70 100644 --- a/README.md +++ b/README.md @@ -114,7 +114,10 @@ which always sends a standard signal (e.g. SIGTERM). Some apps require a different stop signal in order to do graceful cleanup. For example, to rewrite the signal SIGTERM (number 15) to SIGQUIT (number 3), -just add `--rewrite 15:3` on the command line. +just add `--rewrite 15:3` or `--rewrite TERM:QUIT` on the command line. Signal +names can be specified with or without the `SIG` prefix (e.g. `TERM` or +`SIGTERM`). To see the mapping of signal numbers to names on the system in +question, specify `-l/--list`. To drop a signal entirely, you can rewrite it to the special number `0`. @@ -218,6 +221,7 @@ ENTRYPOINT ["/usr/bin/dumb-init", "--"] # or if you use --rewrite or other cli flags # ENTRYPOINT ["dumb-init", "--rewrite", "2:3", "--"] +# ENTRYPOINT ["dumb-init", "--rewrite", "TERM:QUIT", "--"] CMD ["/my/script", "--with", "--args"] ``` diff --git a/dumb-init.c b/dumb-init.c index e1a2fab..ec9445e 100644 --- a/dumb-init.c +++ b/dumb-init.c @@ -46,6 +46,155 @@ pid_t child_pid = -1; char debug = 0; char use_setsid = 1; +typedef struct signame_map_entry { + const int signum; + const char *signame; +} signame_map_entry; + +static const signame_map_entry signame_map[] = { +#ifdef SIGABRT + { SIGABRT, "ABRT" }, +#endif +#ifdef SIGALRM + { SIGALRM, "ALRM" }, +#endif +#ifdef SIGBUS + { SIGBUS, "BUS" }, +#endif +#ifdef SIGCHLD + { SIGCHLD, "CHLD" }, +#endif +#ifdef SIGCONT + { SIGCONT, "CONT" }, +#endif +#ifdef SIGEMT + { SIGEMT, "EMT" }, +#endif +#ifdef SIGFPE + { SIGFPE, "FPE" }, +#endif +#ifdef SIGHUP + { SIGHUP, "HUP" }, +#endif +#ifdef SIGILL + { SIGILL, "ILL" }, +#endif +#ifdef SIGINFO + { SIGINFO, "INFO" }, +#endif +#ifdef SIGINT + { SIGINT, "INT" }, +#endif +#ifdef SIGIO + { SIGIO, "IO" }, +#endif +#ifdef SIGIOT + { SIGIOT, "IOT" }, +#endif +#ifdef SIGKILL + { SIGKILL, "KILL" }, +#endif +#ifdef SIGLOST + { SIGLOST, "LOST" }, +#endif +#ifdef SIGPIPE + { SIGPIPE, "PIPE" }, +#endif +#ifdef SIGPOLL + { SIGPOLL, "POLL" }, +#endif +#ifdef SIGPROF + { SIGPROF, "PROF" }, +#endif +#ifdef SIGPWR + { SIGPWR, "PWR" }, +#endif +#ifdef SIGQUIT + { SIGQUIT, "QUIT" }, +#endif +#ifdef SIGSEGV + { SIGSEGV, "SEGV" }, +#endif +#ifdef SIGSTKFLT + { SIGSTKFLT, "STKFLT" }, +#endif +#ifdef SIGSTOP + { SIGSTOP, "STOP" }, +#endif +#ifdef SIGSYS + { SIGSYS, "SYS" }, +#endif +#ifdef SIGTERM + { SIGTERM, "TERM" }, +#endif +#ifdef SIGTRAP + { SIGTRAP, "TRAP" }, +#endif +#ifdef SIGTSTP + { SIGTSTP, "TSTP" }, +#endif +#ifdef SIGTTIN + { SIGTTIN, "TTIN" }, +#endif +#ifdef SIGTTOU + { SIGTTOU, "TTOU" }, +#endif +#ifdef SIGUNUSED + { SIGUNUSED, "UNUSED" }, +#endif +#ifdef SIGURG + { SIGURG, "URG" }, +#endif +#ifdef SIGUSR1 + { SIGUSR1, "USR1" }, +#endif +#ifdef SIGUSR2 + { SIGUSR2, "USR2" }, +#endif +#ifdef SIGVTALRM + { SIGVTALRM, "VTALRM" }, +#endif +#ifdef SIGWINCH + { SIGWINCH, "WINCH" }, +#endif +#ifdef SIGXCPU + { SIGXCPU, "XCPU" }, +#endif +#ifdef SIGXFSZ + { SIGXFSZ, "XFSZ" }, +#endif +}; +int signame_map_size = sizeof(signame_map) / sizeof(signame_map[0]); + +const char *signum_to_signame(int signum) { + int i; + + for (i = 0; i < signame_map_size; i++) { + if (signame_map[i].signum == signum) { + return signame_map[i].signame; + } + } + + return NULL; +} + +int signame_to_signum(const char *signame) { + int i; + const char *name = signame; + + if (!strncmp(name, "SIG", 3)) { + name += 3; + } + + for (i = 0; i < signame_map_size; i++) { + if (!strcmp(signame_map[i].signame, name)) { + return signame_map[i].signum; + } + } + + return 0; +} + int translate_signal(int signum) { if (signum <= 0 || signum > MAXSIG) { return signum; @@ -54,7 +203,7 @@ int translate_signal(int signum) { if (translated == -1) { return signum; } else { - DEBUG("Translating signal %d to %d.\n", signum, translated); + DEBUG("Translating signal %d (%s) to %d (%s).\n", signum, signum_to_signame(signum), translated, signum_to_signame(translated)); return translated; } } @@ -64,7 +213,7 @@ void forward_signal(int signum) { signum = translate_signal(signum); if (signum != 0) { kill(use_setsid ? -child_pid : child_pid, signum); - DEBUG("Forwarded signal %d to children.\n", signum); + DEBUG("Forwarded signal %d (%s) to children.\n", signum, signum_to_signame(signum)); } else { DEBUG("Not forwarding signal %d to children (ignored).\n", signum); } @@ -91,10 +240,10 @@ void forward_signal(int signum) { * */ void handle_signal(int signum) { - DEBUG("Received signal %d.\n", signum); + DEBUG("Received signal %d (%s).\n", signum, signum_to_signame(signum)); if (signal_temporary_ignores[signum] == 1) { - DEBUG("Ignoring tty hand-off signal %d.\n", signum); + DEBUG("Ignoring tty hand-off signal %d (%s).\n", signum, signum_to_signame(signum)); signal_temporary_ignores[signum] = 0; } else if (signum == SIGCHLD) { int status, exit_status; @@ -106,7 +255,7 @@ void handle_signal(int signum) { } else { assert(WIFSIGNALED(status)); exit_status = 128 + WTERMSIG(status); - DEBUG("A child with PID %d was terminated by signal %d.\n", killed_pid, exit_status - 128); + DEBUG("A child with PID %d was terminated by signal %d (%s).\n", killed_pid, exit_status - 128, signum_to_signame(exit_status - 128)); } if (killed_pid == child_pid) { @@ -137,8 +286,11 @@ void print_help(char *argv[]) { " In this mode, signals are only proxied to the\n" " direct child and not any of its descendants.\n" " -r, --rewrite s:r Rewrite received signal s to new signal r before proxying.\n" - " To ignore (not proxy) a signal, rewrite it to 0.\n" - " This option can be specified multiple times.\n" + " Signals may be specified as numbers or names like USR1 or\n" + " SIGINT (see -l/--list). To ignore (not proxy) a signal,\n" + " rewrite it to 0. This option can be specified multiple\n" + " times.\n" + " -l, --list Print signal number to name mapping and exit.\n" " -v, --verbose Print debugging information to stderr.\n" " -h, --help Print this help message and exit.\n" " -V, --version Print the current version and exit.\n" @@ -153,7 +305,7 @@ void print_rewrite_signum_help() { fprintf( stderr, "Usage: -r option takes :, where " - "is between 1 and %d.\n" + "is between 1 and %d, specified by number or name.\n" "This option can be specified multiple times.\n" "Use --help for full usage.\n", MAXSIG @@ -161,12 +313,41 @@ void print_rewrite_signum_help() { exit(1); } +int scansignal(const char *arg, int min, int *pos) { + int signum, outpos; + char signame[10]; + + if ( + sscanf(arg, "%d%n", &signum, &outpos) == 1 + && (signum >= min && signum <= MAXSIG) + ) { + *pos = outpos; + return signum; + } else if ( + ( + sscanf(arg, "SIG%9[A-Z0-9]%n", signame, &outpos) == 1 + || sscanf(arg, "%9[A-Z0-9]%n", signame, &outpos) == 1 + ) + && (signum = signame_to_signum(signame)) + && (signum >= min && signum <= MAXSIG) + ) { + *pos = outpos; + return signum; + } else { + print_rewrite_signum_help(); + } + + return -1; +} + void parse_rewrite_signum(char *arg) { - int signum, replacement; + int signum, replacement, pos; + if ( - sscanf(arg, "%d:%d", &signum, &replacement) == 2 && - (signum >= 1 && signum <= MAXSIG) && - (replacement >= 0 && replacement <= MAXSIG) + (signum = scansignal(arg, 1, &pos)) >= 1 + && arg[pos] == ':' + && (replacement = scansignal(arg += pos + 1, 0, &pos)) >= 0 + && arg[pos] == 0 ) { signal_rewrite[signum] = replacement; } else { @@ -181,16 +362,17 @@ void set_rewrite_to_sigstop_if_not_defined(int signum) { } char **parse_command(int argc, char *argv[]) { - int opt; + int opt, i; struct option long_options[] = { {"help", no_argument, NULL, 'h'}, {"single-child", no_argument, NULL, 'c'}, {"rewrite", required_argument, NULL, 'r'}, + {"list", no_argument, NULL, 'l'}, {"verbose", no_argument, NULL, 'v'}, {"version", no_argument, NULL, 'V'}, {NULL, 0, NULL, 0}, }; - while ((opt = getopt_long(argc, argv, "+hvVcr:", long_options, NULL)) != -1) { + while ((opt = getopt_long(argc, argv, "+hvVlcr:", long_options, NULL)) != -1) { switch (opt) { case 'h': print_help(argv); @@ -200,6 +382,12 @@ char **parse_command(int argc, char *argv[]) { break; case 'V': fprintf(stderr, "dumb-init v%s", VERSION); + exit(0); + case 'l': + for (i = 1; i <= MAXSIG; i++) { + fprintf(stderr, "%2d: %s\n", i, signum_to_signame(i)); + } + exit(0); case 'c': use_setsid = 0; diff --git a/tests/cli_test.py b/tests/cli_test.py index 27d7bf2..253a59e 100644 --- a/tests/cli_test.py +++ b/tests/cli_test.py @@ -55,8 +55,11 @@ def test_help_message(flag, current_version): b' In this mode, signals are only proxied to the\n' b' direct child and not any of its descendants.\n' b' -r, --rewrite s:r Rewrite received signal s to new signal r before proxying.\n' - b' To ignore (not proxy) a signal, rewrite it to 0.\n' - b' This option can be specified multiple times.\n' + b' Signals may be specified as numbers or names like USR1 or\n' + b' SIGINT (see -l/--list). To ignore (not proxy) a signal,\n' + b' rewrite it to 0. This option can be specified multiple\n' + b' times.\n' + b' -l, --list Print signal number to name mapping and exit.\n' b' -v, --verbose Print debugging information to stderr.\n' b' -h, --help Print this help message and exit.\n' b' -V, --version Print the current version and exit.\n' @@ -90,9 +93,9 @@ def test_verbose(flag): ( '(^|\n)\\[dumb-init\\] Child spawned with PID [0-9]+\\.\n' '.*' # child might print here - '\\[dumb-init\\] Received signal {signal.SIGCHLD}\\.\n' + '\\[dumb-init\\] Received signal {signal.SIGCHLD} \\(CHLD\\)\\.\n' '\\[dumb-init\\] A child with PID [0-9]+ exited with exit status 0.\n' - '\\[dumb-init\\] Forwarded signal 15 to children\\.\n' + '\\[dumb-init\\] Forwarded signal 15 \\(TERM\\) to children\\.\n' '\\[dumb-init\\] Child exited with status 0\\. Goodbye\\.\n$' ).format(signal=signal).encode('utf8'), stderr, @@ -111,9 +114,9 @@ def test_verbose_and_single_child(flag1, flag2): assert re.match( ( '^\\[dumb-init\\] Child spawned with PID [0-9]+\\.\n' - '\\[dumb-init\\] Received signal {signal.SIGCHLD}\\.\n' + '\\[dumb-init\\] Received signal {signal.SIGCHLD} \\(CHLD\\)\\.\n' '\\[dumb-init\\] A child with PID [0-9]+ exited with exit status 0.\n' - '\\[dumb-init\\] Forwarded signal 15 to children\\.\n' + '\\[dumb-init\\] Forwarded signal 15 \\(TERM\\) to children\\.\n' '\\[dumb-init\\] Child exited with status 0\\. Goodbye\\.\n$' ).format(signal=signal).encode('utf8'), stderr, @@ -129,6 +132,9 @@ def test_verbose_and_single_child(flag1, flag2): ('-r', '15'), ('-r', '15::12'), ('-r', '15:derp'), + ('-r', '15:SIGBAR'), + ('-r', 'TERM:SIGBAR'), + ('-r', 'FOO:12'), ('-r', '15:12', '-r'), ('-r', '15:12', '-r', '0'), ('-r', '15:12', '-r', '1:32'), @@ -144,7 +150,7 @@ def test_rewrite_errors(extra_args): assert proc.returncode == 1 assert stderr == ( b'Usage: -r option takes :, where ' - b'is between 1 and 31.\n' + b'is between 1 and 31, specified by number or name.\n' b'This option can be specified multiple times.\n' b'Use --help for full usage.\n' ) diff --git a/tests/proxies_signals_test.py b/tests/proxies_signals_test.py index 961ae58..82fb22e 100644 --- a/tests/proxies_signals_test.py +++ b/tests/proxies_signals_test.py @@ -105,3 +105,21 @@ def test_ignored_signals_are_not_proxied(): proc.send_signal(signal.SIGWINCH) proc.send_signal(signal.SIGHUP) assert proc.stdout.readline() == '{}\n'.format(signal.SIGHUP).encode('ascii') + + +@pytest.mark.usefixtures('both_debug_modes', 'both_setsid_modes') +def test_signal_names(): + """Ensure dumb-init can map signal names.""" + rewrite_map = { + 'TERM': 'QUIT', + 'SIGINT': 0, + 'WINCH': 0, + } + with print_signals(_rewrite_map_to_args(rewrite_map)) as (proc, _): + proc.send_signal(signal.SIGTERM) + proc.send_signal(signal.SIGINT) + assert proc.stdout.readline() == '{}\n'.format(signal.SIGQUIT).encode('ascii') + + proc.send_signal(signal.SIGWINCH) + proc.send_signal(signal.SIGHUP) + assert proc.stdout.readline() == '{}\n'.format(signal.SIGHUP).encode('ascii') diff --git a/tests/tty_test.py b/tests/tty_test.py index e7b15ad..d7edb2b 100644 --- a/tests/tty_test.py +++ b/tests/tty_test.py @@ -111,8 +111,8 @@ def test_sighup_sigcont_ignored_if_was_session_leader(): output = readall(fd).decode('UTF-8') - assert 'Ignoring tty hand-off signal {}.'.format(signal.SIGHUP) in output - assert 'Ignoring tty hand-off signal {}.'.format(signal.SIGCONT) in output + assert 'Ignoring tty hand-off signal {} (HUP).'.format(signal.SIGHUP) in output + assert 'Ignoring tty hand-off signal {} (CONT).'.format(signal.SIGCONT) in output - assert '[dumb-init] Forwarded signal {} to children.'.format(signal.SIGHUP) in output - assert '[dumb-init] Forwarded signal {} to children.'.format(signal.SIGCONT) not in output + assert '[dumb-init] Forwarded signal {} (HUP) to children.'.format(signal.SIGHUP) in output + assert '[dumb-init] Forwarded signal {} (CONT) to children.'.format(signal.SIGCONT) not in output