mirror of https://github.com/krallin/tini.git
Ensure Tini doesn't lock up on of tty ownership
In the rare scenario where Tini is not running as PID 1 and its child (or something else) sets TOSTOP on the tty, then Tini would hang when if it tries to write debug messages. This commit fixes this problem by ignoring SIGTTOU. In the also rare scenario where two Tini instances are running, the tty-passing could end up being done improperly (if the parent Tini instance passes the tty to the child before the grandparent Tini instance passes it to the parent Tini instance), and result in the parent Tini instance running in the foreground. This commit fixes this problem by passing the tty in the child (which we can do because we are ignoring SIGTTOU).
This commit is contained in:
parent
82acbc5ccc
commit
88342efc3c
132
src/tini.c
132
src/tini.c
|
@ -24,6 +24,12 @@
|
|||
|
||||
#define ARRAY_LEN(x) (sizeof(x) / sizeof(x[0]))
|
||||
|
||||
typedef struct {
|
||||
sigset_t* const sigmask_ptr;
|
||||
struct sigaction* const sigttin_action_ptr;
|
||||
struct sigaction* const sigttou_action_ptr;
|
||||
} signal_configuration_t;
|
||||
|
||||
|
||||
#ifdef PR_SET_CHILD_SUBREAPER
|
||||
#define HAS_SUBREAPER 1
|
||||
|
@ -55,24 +61,71 @@ static const char reaper_warning[] = "Tini is not running as PID 1 "
|
|||
#endif
|
||||
"run Tini as PID 1.";
|
||||
|
||||
int restore_signals(const signal_configuration_t* const sigconf_ptr) {
|
||||
if (sigprocmask(SIG_SETMASK, sigconf_ptr->sigmask_ptr, NULL)) {
|
||||
PRINT_FATAL("Restoring child signal mask failed: '%s'", strerror(errno));
|
||||
return 1;
|
||||
}
|
||||
|
||||
int spawn(const sigset_t* const child_sigset_ptr, char* const argv[], int* const child_pid_ptr) {
|
||||
if (sigaction(SIGTTIN, sigconf_ptr->sigttin_action_ptr, NULL)) {
|
||||
PRINT_FATAL("Restoring SIGTTIN handler failed: '%s'", strerror((errno)));
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (sigaction(SIGTTOU, sigconf_ptr->sigttou_action_ptr, NULL)) {
|
||||
PRINT_FATAL("Restoring SIGTTOU handler failed: '%s'", strerror((errno)));
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int isolate_child() {
|
||||
// Put the child into a new process group.
|
||||
if (setpgid(0, 0) < 0) {
|
||||
PRINT_FATAL("setpgid failed: '%s'", strerror(errno));
|
||||
return 1;
|
||||
}
|
||||
|
||||
// If there is a tty, allocate it to this new process group. We
|
||||
// can do this in the child process because we're blocking
|
||||
// SIGTTIN / SIGTTOU.
|
||||
|
||||
// Doing it in the child process avoids a race condition scenario
|
||||
// if Tini is calling Tini (in which case the grandparent may make the
|
||||
// parent the foreground process group, and the actual child ends up...
|
||||
// in the background!)
|
||||
if (tcsetpgrp(STDIN_FILENO, getpgrp())) {
|
||||
if (errno == ENOTTY) {
|
||||
PRINT_DEBUG("tcsetpgrp failed: no tty (ok to proceed)")
|
||||
} else {
|
||||
PRINT_FATAL("tcsetpgrp failed: '%s'", strerror(errno));
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int spawn(const signal_configuration_t* const sigconf_ptr, char* const argv[], int* const child_pid_ptr) {
|
||||
pid_t pid;
|
||||
|
||||
// TODO: check if tini was a foreground process to begin with (it's not OK to "steal" the foreground!")
|
||||
|
||||
pid = fork();
|
||||
if (pid < 0) {
|
||||
PRINT_FATAL("Fork failed: '%s'", strerror(errno));
|
||||
return 1;
|
||||
} else if (pid == 0) {
|
||||
// Child
|
||||
if (sigprocmask(SIG_SETMASK, child_sigset_ptr, NULL)) {
|
||||
PRINT_FATAL("Setting child signal mask failed: '%s'", strerror(errno));
|
||||
|
||||
// Put the child in a process group and make it the foreground process if there is a tty.
|
||||
if (isolate_child()) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Put the child into a new process group
|
||||
if (setpgid(0, 0) < 0) {
|
||||
PRINT_FATAL("setpgid failed: '%s'", strerror(errno));
|
||||
// Restore all signal handlers to the way they were before we touched them.
|
||||
if (restore_signals(sigconf_ptr)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
@ -83,17 +136,6 @@ int spawn(const sigset_t* const child_sigset_ptr, char* const argv[], int* const
|
|||
// Parent
|
||||
PRINT_INFO("Spawned child process '%s' with pid '%i'", argv[0], pid);
|
||||
*child_pid_ptr = pid;
|
||||
|
||||
// If there is a tty, pass control over to the child process group
|
||||
if (tcsetpgrp(STDIN_FILENO, pid)) {
|
||||
if (errno == ENOTTY) {
|
||||
PRINT_DEBUG("tcsetpgrp failed: no tty (ok to proceed)")
|
||||
} else {
|
||||
PRINT_FATAL("tcsetpgrp failed: '%s'", strerror(errno));
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
@ -218,27 +260,48 @@ void reaper_check () {
|
|||
}
|
||||
|
||||
|
||||
int prepare_sigmask(sigset_t* const parent_sigset_ptr, sigset_t* const child_sigset_ptr) {
|
||||
/* Prepare signals to block; make sure we don't block program error signals. */
|
||||
int configure_signals(sigset_t* const parent_sigset_ptr, const signal_configuration_t* const sigconf_ptr) {
|
||||
/* Block all signals that are meant to be collected by the main loop */
|
||||
if (sigfillset(parent_sigset_ptr)) {
|
||||
PRINT_FATAL("sigfillset failed: '%s'", strerror(errno));
|
||||
return 1;
|
||||
}
|
||||
|
||||
// These ones shouldn't be collected by the main loop
|
||||
uint i;
|
||||
int ignore_signals[] = {SIGFPE, SIGILL, SIGSEGV, SIGBUS, SIGABRT, SIGTRAP, SIGSYS} ;
|
||||
for (i = 0; i < ARRAY_LEN(ignore_signals); i++) {
|
||||
if (sigdelset(parent_sigset_ptr, ignore_signals[i])) {
|
||||
PRINT_FATAL("sigdelset failed: '%i'", ignore_signals[i]);
|
||||
int signals_for_tini[] = {SIGFPE, SIGILL, SIGSEGV, SIGBUS, SIGABRT, SIGTRAP, SIGSYS, SIGTTIN, SIGTTOU};
|
||||
for (i = 0; i < ARRAY_LEN(signals_for_tini); i++) {
|
||||
if (sigdelset(parent_sigset_ptr, signals_for_tini[i])) {
|
||||
PRINT_FATAL("sigdelset failed: '%i'", signals_for_tini[i]);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (sigprocmask(SIG_SETMASK, parent_sigset_ptr, child_sigset_ptr)) {
|
||||
if (sigprocmask(SIG_SETMASK, parent_sigset_ptr, sigconf_ptr->sigmask_ptr)) {
|
||||
PRINT_FATAL("sigprocmask failed: '%s'", strerror(errno));
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Handle SIGTTIN and SIGTTOU separately. Since Tini makes the child process group
|
||||
// the foreground process group, there's a chance Tini can end up not controlling the tty.
|
||||
// If TOSTOP is set on the tty, this could block Tini on writing debug messages. We don't
|
||||
// want that. Ignore those signals.
|
||||
struct sigaction ign_action;
|
||||
memset(&ign_action, 0, sizeof ign_action);
|
||||
|
||||
ign_action.sa_handler = SIG_IGN;
|
||||
sigemptyset(&ign_action.sa_mask);
|
||||
|
||||
if (sigaction(SIGTTIN, &ign_action, sigconf_ptr->sigttin_action_ptr)) {
|
||||
PRINT_FATAL("Failed to ignore SIGTTIN");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (sigaction(SIGTTOU, &ign_action, sigconf_ptr->sigttou_action_ptr)) {
|
||||
PRINT_FATAL("Failed to ignore SIGTTOU");
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -356,10 +419,19 @@ int main(int argc, char *argv[]) {
|
|||
return 1;
|
||||
}
|
||||
|
||||
/* Prepare sigmask */
|
||||
sigset_t parent_sigset;
|
||||
sigset_t child_sigset;
|
||||
if (prepare_sigmask(&parent_sigset, &child_sigset)) {
|
||||
/* Configure signals */
|
||||
sigset_t parent_sigset, child_sigset;
|
||||
struct sigaction sigttin_action, sigttou_action;
|
||||
memset(&sigttin_action, 0, sizeof sigttin_action);
|
||||
memset(&sigttou_action, 0, sizeof sigttou_action);
|
||||
|
||||
signal_configuration_t child_sigconf = {
|
||||
.sigmask_ptr = &child_sigset,
|
||||
.sigttin_action_ptr = &sigttin_action,
|
||||
.sigttou_action_ptr = &sigttou_action,
|
||||
};
|
||||
|
||||
if (configure_signals(&parent_sigset, &child_sigconf)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
@ -374,7 +446,7 @@ int main(int argc, char *argv[]) {
|
|||
reaper_check();
|
||||
|
||||
/* Go on */
|
||||
if (spawn(&child_sigset, *child_args_ptr, &child_pid)) {
|
||||
if (spawn(&child_sigconf, *child_args_ptr, &child_pid)) {
|
||||
return 1;
|
||||
}
|
||||
free(child_args_ptr);
|
||||
|
|
Loading…
Reference in New Issue