diff options
author | Laurent Bercot <ska-skaware@skarnet.org> | 2022-01-13 19:10:58 +0000 |
---|---|---|
committer | Laurent Bercot <ska@appnovation.com> | 2022-01-13 19:10:58 +0000 |
commit | 654ab33e97830a4eba4427407776f85abd6f95cf (patch) | |
tree | 9fa29c8a6c69e8e1ada9fe3d2008d22083d3e2d3 /src/init | |
parent | 0840786b5826d81b447e4cd4292f0615b17a15ae (diff) | |
download | s6-linux-init-654ab33e97830a4eba4427407776f85abd6f95cf.tar.xz |
Add satanic verses for ctty-passing in s6-l-i
Signed-off-by: Laurent Bercot <ska@appnovation.com>
Diffstat (limited to 'src/init')
-rw-r--r-- | src/init/s6-linux-init.c | 89 |
1 files changed, 82 insertions, 7 deletions
diff --git a/src/init/s6-linux-init.c b/src/init/s6-linux-init.c index e88bccf..4168523 100644 --- a/src/init/s6-linux-init.c +++ b/src/init/s6-linux-init.c @@ -6,6 +6,7 @@ #include <stdlib.h> #include <errno.h> #include <signal.h> +#include <termios.h> #include <sys/stat.h> #include <sys/mount.h> #include <sys/reboot.h> @@ -99,7 +100,6 @@ static inline void run_stage2 (char const *basedir, char const **argv, unsigned for (unsigned int i = 0 ; i < argc ; i++) childargv[i+2] = argv[i] ; childargv[argc + 2] = 0 ; - if (!inns) setsid() ; if (nologger) { close(notifpipe[1]) ; @@ -117,6 +117,82 @@ static inline void run_stage2 (char const *basedir, char const **argv, unsigned xmexec_fm(childargv, envp, envlen, modifs, modiflen) ; } +/* + This is ugly voodoo, keep away from innocent eyes. + If stdin is a terminal, it means we're in a "docker run -it" container +(or equivalent) and stdin is a ctty. We don't want the supervision tree to +have this ctty (else ^C kills everything) but we want stage 2 to keep it, +so that if we have a CMD run by stage 2 (as is the case with s6-overlay), +it remains interactive (control sequences can be sent to it). + In order to achieve that, we have the child (future stage 2) steal the +ctty from the parent (future s6-svscan). But that may not work, for instance +in a USER container, where we don't have the appropriate permissions; in +which case the parent must be informed that the operation failed, so it can +relinquish the ctty itself - the child won't get the ctty but at least the +parent won't keep it. + Transmission of the information is done via a pipe, it's the simplest +way to properly order operations. + If the parent abandons its ctty itself, the child needs to protect against +the SIGHUP it receives when it happens. +*/ + +static inline pid_t fork_and_setup_session (int ttyfd) /* closes ttyfd */ +{ + pid_t pid ; + if (ttyfd < 0) /* the common case is simple */ + { + pid = fork() ; + if (pid == -1) strerr_diefu1sys(111, "fork") ; + setsid() ; + } + else /* let the insanity begin */ + { + int p[2] ; + if (pipe(p) == -1) strerr_diefu1sys(111, "pipe") ; + pid = fork() ; + if (pid == -1) strerr_diefu1sys(111, "fork") ; + if (!pid) + { /* child */ + PROG = "s6-linux-init (child)" ; + close(p[0]) ; + if (setsid() == -1) strerr_diefu1sys(111, "setsid") ; + if (fd_move(0, ttyfd) == -1) strerr_diefu1sys(111, "move stdin fd") ; + if (ioctl(0, TIOCSCTTY, 1) == -1) + { + strerr_warnwu1sys("grab controlling terminal") ; + if (!sig_altignore(SIGHUP)) + strerr_diefu1sys(111, "ignore SIGHUP") ; + if (write(p[1], "1", 1) < 1) /* '1' == problem */ + strerr_diefu1sys(111, "write to parent") ; + } + else if (write(p[1], "0", 1) < 1) /* '0' == ok */ + strerr_diefu1sys(111, "write to parent") ; + close(p[1]) ; + } + else + { /* parent */ + int r ; + char c ; + close(p[1]) ; + r = read(p[0], &c, 1) ; + if (r == -1) + strerr_diefu1sys(111, "read from child") ; + else if (!r) + strerr_dief1x(111, "child died") ; + else if (c == '1') + { + if (ioctl(ttyfd, TIOCNOTTY) == -1) + strerr_warnwu1sys("relinquish controlling terminal") ; + } + else if (c != '0') + strerr_dief1x(100, "bad parent/child protocol") ; + close(p[0]) ; + close(ttyfd) ; + } + } + return pid ; +} + int main (int argc, char const **argv, char const *const *envp) { mode_t mask = 0022 ; @@ -127,6 +203,7 @@ int main (int argc, char const **argv, char const *const *envp) char const *initdefault = "default" ; int mounttype = 1 ; int hasconsole = 1 ; + int ttyfd = -1 ; stralloc envmodifs = STRALLOC_ZERO ; PROG = "s6-linux-init" ; @@ -162,7 +239,7 @@ int main (int argc, char const **argv, char const *const *envp) if (fcntl(1, F_GETFD) < 0) hasconsole = 0 ; if (inns) - { /* If there's a Docker synchronization pipe, wait on it */ + { char c ; ssize_t r = read(3, &c, 1) ; if (r < 0) @@ -174,6 +251,7 @@ int main (int argc, char const **argv, char const *const *envp) if (r) strerr_warnw1x("parent wrote to fd 3!") ; close(3) ; } + if (isatty(0) && !slashdev) ttyfd = dup(0) ; } else if (hasconsole) allwrite(1, BANNER, sizeof(BANNER) - 1) ; if (chdir("/") == -1) strerr_diefu1sys(111, "chdir to /") ; @@ -256,7 +334,6 @@ int main (int argc, char const **argv, char const *const *envp) { char const *newenvp[2] = { 0, 0 } ; size_t pathlen = path ? strlen(path) : 0 ; - pid_t pid ; char fmtfd[2 + UINT_FMT] = "-" ; char const *newargv[5] = { S6_EXTBINPREFIX "s6-svscan", fmtfd, "--", S6_LINUX_INIT_TMPFS "/" SCANDIR, 0 } ; char pathvar[6 + pathlen] ; @@ -269,10 +346,8 @@ int main (int argc, char const **argv, char const *const *envp) newenvp[0] = pathvar ; } if (nologger && pipe(notifpipe) < 0) strerr_diefu1sys(111, "pipe") ; - pid = fork() ; - if (pid == -1) strerr_diefu1sys(111, "fork") ; - if (!pid) run_stage2(basedir, argv, argc, newenvp, !!path, envmodifs.s, envmodifs.len, initdefault) ; - setsid() ; + if (!fork_and_setup_session(ttyfd)) + run_stage2(basedir, argv, argc, newenvp, !!path, envmodifs.s, envmodifs.len, initdefault) ; if (nologger) { close(notifpipe[0]) ; |