From 00a0ecf55423f79e2aea15b5469bde0d538706cd Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 2 Mar 2017 12:53:06 -0800 Subject: [PATCH] Add separate console socket Signed-off-by: Michael Crosby --- libcontainer/console_linux.go | 6 ++ libcontainer/container_linux.go | 37 +++---- libcontainer/factory_linux.go | 16 ++- libcontainer/init_linux.go | 42 +++----- libcontainer/integration/execin_test.go | 30 +++++- libcontainer/process.go | 19 +--- libcontainer/process_linux.go | 41 +------- libcontainer/setns_init_linux.go | 7 +- libcontainer/standard_init_linux.go | 11 ++- libcontainer/sync.go | 13 +-- libcontainer/utils/utils_unix.go | 10 ++ restore.go | 2 +- tty.go | 36 +++---- utils_linux.go | 123 ++++++++++++------------ 14 files changed, 178 insertions(+), 215 deletions(-) diff --git a/libcontainer/console_linux.go b/libcontainer/console_linux.go index 6e38b462..e431766b 100644 --- a/libcontainer/console_linux.go +++ b/libcontainer/console_linux.go @@ -7,6 +7,12 @@ import ( "unsafe" ) +func ConsoleFromFile(f *os.File) Console { + return &linuxConsole{ + master: f, + } +} + // newConsole returns an initialized console that can be used within a container by copying bytes // from the master side to the slave that is attached as the tty for the container's init process. func newConsole() (Console, error) { diff --git a/libcontainer/container_linux.go b/libcontainer/container_linux.go index 7a4b86c4..28dff866 100644 --- a/libcontainer/container_linux.go +++ b/libcontainer/container_linux.go @@ -335,7 +335,7 @@ func (c *linuxContainer) deleteExecFifo() { } func (c *linuxContainer) newParentProcess(p *Process, doInit bool) (parentProcess, error) { - parentPipe, childPipe, err := newPipe() + parentPipe, childPipe, err := utils.NewSockPair("init") if err != nil { return nil, newSystemErrorWithCause(err, "creating new init pipe") } @@ -370,9 +370,17 @@ func (c *linuxContainer) commandTemplate(p *Process, childPipe *os.File) (*exec. if cmd.SysProcAttr == nil { cmd.SysProcAttr = &syscall.SysProcAttr{} } - cmd.ExtraFiles = append(p.ExtraFiles, childPipe) + cmd.ExtraFiles = append(cmd.ExtraFiles, p.ExtraFiles...) + if p.ConsoleSocket != nil { + cmd.ExtraFiles = append(cmd.ExtraFiles, p.ConsoleSocket) + cmd.Env = append(cmd.Env, + fmt.Sprintf("_LIBCONTAINER_CONSOLE=%d", stdioFdCount+len(cmd.ExtraFiles)-1), + ) + } + cmd.ExtraFiles = append(cmd.ExtraFiles, childPipe) cmd.Env = append(cmd.Env, - fmt.Sprintf("_LIBCONTAINER_INITPIPE=%d", stdioFdCount+len(cmd.ExtraFiles)-1)) + fmt.Sprintf("_LIBCONTAINER_INITPIPE=%d", stdioFdCount+len(cmd.ExtraFiles)-1), + ) // NOTE: when running a container with no PID namespace and the parent process spawning the container is // PID1 the pdeathsig is being delivered to the container's init process by the kernel for some reason // even with the parent still running. @@ -395,7 +403,6 @@ func (c *linuxContainer) newInitProcess(p *Process, cmd *exec.Cmd, parentPipe, c if err != nil { return nil, err } - p.consoleChan = make(chan *os.File, 1) return &initProcess{ cmd: cmd, childPipe: childPipe, @@ -422,8 +429,6 @@ func (c *linuxContainer) newSetnsProcess(p *Process, cmd *exec.Cmd, parentPipe, if err != nil { return nil, err } - // TODO: set on container for process management - p.consoleChan = make(chan *os.File, 1) return &setnsProcess{ cmd: cmd, cgroupPaths: c.cgroupManager.GetPaths(), @@ -463,28 +468,10 @@ func (c *linuxContainer) newInitConfig(process *Process) *initConfig { if len(process.Rlimits) > 0 { cfg.Rlimits = process.Rlimits } - /* - * TODO: This should not be automatically computed. We should implement - * this as a field in libcontainer.Process, and then we only dup the - * new console over the file descriptors which were not explicitly - * set with process.Std{in,out,err}. The reason I've left this as-is - * is because the GetConsole() interface is new, there's no need to - * polish this interface right now. - */ - if process.Stdin == nil && process.Stdout == nil && process.Stderr == nil { - cfg.CreateConsole = true - } + cfg.CreateConsole = process.ConsoleSocket != nil return cfg } -func newPipe() (parent *os.File, child *os.File, err error) { - fds, err := syscall.Socketpair(syscall.AF_LOCAL, syscall.SOCK_STREAM|syscall.SOCK_CLOEXEC, 0) - if err != nil { - return nil, nil, err - } - return os.NewFile(uintptr(fds[1]), "parent"), os.NewFile(uintptr(fds[0]), "child"), nil -} - func (c *linuxContainer) Destroy() error { c.m.Lock() defer c.m.Unlock() diff --git a/libcontainer/factory_linux.go b/libcontainer/factory_linux.go index 7f044bd4..d5532875 100644 --- a/libcontainer/factory_linux.go +++ b/libcontainer/factory_linux.go @@ -222,8 +222,10 @@ func (l *LinuxFactory) Type() string { func (l *LinuxFactory) StartInitialization() (err error) { var ( pipefd, rootfd int + consoleSocket *os.File envInitPipe = os.Getenv("_LIBCONTAINER_INITPIPE") envStateDir = os.Getenv("_LIBCONTAINER_STATEDIR") + envConsole = os.Getenv("_LIBCONTAINER_CONSOLE") ) // Get the INITPIPE. @@ -241,12 +243,20 @@ func (l *LinuxFactory) StartInitialization() (err error) { // Only init processes have STATEDIR. rootfd = -1 if it == initStandard { - rootfd, err = strconv.Atoi(envStateDir) - if err != nil { + if rootfd, err = strconv.Atoi(envStateDir); err != nil { return fmt.Errorf("unable to convert _LIBCONTAINER_STATEDIR=%s to int: %s", envStateDir, err) } } + if envConsole != "" { + console, err := strconv.Atoi(envConsole) + if err != nil { + return fmt.Errorf("unable to convert _LIBCONTAINER_CONSOLE=%s to int: %s", envConsole, err) + } + consoleSocket = os.NewFile(uintptr(console), "console-socket") + defer consoleSocket.Close() + } + // clear the current process's environment to clean any libcontainer // specific env vars. os.Clearenv() @@ -269,7 +279,7 @@ func (l *LinuxFactory) StartInitialization() (err error) { } }() - i, err := newContainerInit(it, pipe, rootfd) + i, err := newContainerInit(it, pipe, consoleSocket, rootfd) if err != nil { return err } diff --git a/libcontainer/init_linux.go b/libcontainer/init_linux.go index d17b4c8a..ba755da8 100644 --- a/libcontainer/init_linux.go +++ b/libcontainer/init_linux.go @@ -66,7 +66,7 @@ type initer interface { Init() error } -func newContainerInit(t initType, pipe *os.File, stateDirFD int) (initer, error) { +func newContainerInit(t initType, pipe *os.File, consoleSocket *os.File, stateDirFD int) (initer, error) { var config *initConfig if err := json.NewDecoder(pipe).Decode(&config); err != nil { return nil, err @@ -77,15 +77,17 @@ func newContainerInit(t initType, pipe *os.File, stateDirFD int) (initer, error) switch t { case initSetns: return &linuxSetnsInit{ - pipe: pipe, - config: config, + pipe: pipe, + consoleSocket: consoleSocket, + config: config, }, nil case initStandard: return &linuxStandardInit{ - pipe: pipe, - parentPid: syscall.Getppid(), - config: config, - stateDirFD: stateDirFD, + pipe: pipe, + consoleSocket: consoleSocket, + parentPid: syscall.Getppid(), + config: config, + stateDirFD: stateDirFD, }, nil } return nil, fmt.Errorf("unknown init type %q", t) @@ -155,7 +157,8 @@ func finalizeNamespace(config *initConfig) error { // consoles are scoped to a container properly (see runc#814 and the many // issues related to that). This has to be run *after* we've pivoted to the new // rootfs (and the users' configuration is entirely set up). -func setupConsole(pipe *os.File, config *initConfig, mount bool) error { +func setupConsole(socket *os.File, config *initConfig, mount bool) error { + defer socket.Close() // At this point, /dev/ptmx points to something that we would expect. We // used to change the owner of the slave path, but since the /dev/pts mount // can have gid=X set (at the users' option). So touching the owner of the @@ -174,37 +177,16 @@ func setupConsole(pipe *os.File, config *initConfig, mount bool) error { if !ok { return fmt.Errorf("failed to cast console to *linuxConsole") } - // Mount the console inside our rootfs. if mount { if err := linuxConsole.mount(); err != nil { return err } } - - if err := writeSync(pipe, procConsole); err != nil { - return err - } - - // We need to have a two-way synchronisation here. Though it might seem - // pointless, it's important to make sure that the sendmsg(2) payload - // doesn't get swallowed by an out-of-place read(2) [which happens if the - // syscalls get reordered so that sendmsg(2) is before the other side's - // read(2) of procConsole]. - if err := readSync(pipe, procConsoleReq); err != nil { - return err - } - // While we can access console.master, using the API is a good idea. - if err := utils.SendFd(pipe, linuxConsole.File()); err != nil { + if err := utils.SendFd(socket, linuxConsole.File()); err != nil { return err } - - // Make sure the other side received the fd. - if err := readSync(pipe, procConsoleAck); err != nil { - return err - } - // Now, dup over all the things. return linuxConsole.dupStdio() } diff --git a/libcontainer/integration/execin_test.go b/libcontainer/integration/execin_test.go index f714f2df..019757f6 100644 --- a/libcontainer/integration/execin_test.go +++ b/libcontainer/integration/execin_test.go @@ -13,6 +13,7 @@ import ( "github.com/opencontainers/runc/libcontainer" "github.com/opencontainers/runc/libcontainer/configs" + "github.com/opencontainers/runc/libcontainer/utils" ) func TestExecIn(t *testing.T) { @@ -279,9 +280,36 @@ func TestExecInTTY(t *testing.T) { Args: []string{"ps"}, Env: standardEnvironment, } + parent, child, err := utils.NewSockPair("console") + if err != nil { + ok(t, err) + } + defer parent.Close() + defer child.Close() + ps.ConsoleSocket = child + type cdata struct { + c libcontainer.Console + err error + } + dc := make(chan *cdata, 1) + go func() { + f, err := utils.RecvFd(parent) + if err != nil { + dc <- &cdata{ + err: err, + } + } + dc <- &cdata{ + c: libcontainer.ConsoleFromFile(f), + } + }() err = container.Run(ps) ok(t, err) - console, err := ps.GetConsole() + data := <-dc + if data.err != nil { + ok(t, data.err) + } + console := data.c copy := make(chan struct{}) go func() { io.Copy(&stdout, console) diff --git a/libcontainer/process.go b/libcontainer/process.go index 6feea55d..f1ad0814 100644 --- a/libcontainer/process.go +++ b/libcontainer/process.go @@ -47,10 +47,6 @@ type Process struct { // ExtraFiles specifies additional open files to be inherited by the container ExtraFiles []*os.File - // consoleChan provides the masterfd console. - // TODO: Make this persistent in Process. - consoleChan chan *os.File - // Capabilities specify the capabilities to keep when executing the process inside the container // All capabilities not specified will be dropped from the processes capability mask Capabilities *configs.Capabilities @@ -69,6 +65,9 @@ type Process struct { // If Rlimits are not set, the container will inherit rlimits from the parent process Rlimits []configs.Rlimit + // ConsoleSocket provides the masterfd console. + ConsoleSocket *os.File + ops processOperations } @@ -105,15 +104,3 @@ type IO struct { Stdout io.ReadCloser Stderr io.ReadCloser } - -func (p *Process) GetConsole() (Console, error) { - consoleFd, ok := <-p.consoleChan - if !ok { - return nil, fmt.Errorf("failed to get console from process") - } - - // TODO: Fix this so that it used the console API. - return &linuxConsole{ - master: consoleFd, - }, nil -} diff --git a/libcontainer/process_linux.go b/libcontainer/process_linux.go index d3576af4..0f79a381 100644 --- a/libcontainer/process_linux.go +++ b/libcontainer/process_linux.go @@ -100,25 +100,6 @@ func (p *setnsProcess) start() (err error) { ierr := parseSync(p.parentPipe, func(sync *syncT) error { switch sync.Type { - case procConsole: - if err := writeSync(p.parentPipe, procConsoleReq); err != nil { - return newSystemErrorWithCause(err, "writing syncT 'request fd'") - } - - masterFile, err := utils.RecvFd(p.parentPipe) - if err != nil { - return newSystemErrorWithCause(err, "getting master pty from child pipe") - } - - if p.process.consoleChan == nil { - // TODO: Don't panic here, do something more sane. - panic("consoleChan is nil") - } - p.process.consoleChan <- masterFile - - if err := writeSync(p.parentPipe, procConsoleAck); err != nil { - return newSystemErrorWithCause(err, "writing syncT 'ack fd'") - } case procReady: // This shouldn't happen. panic("unexpected procReady in setns") @@ -128,7 +109,6 @@ func (p *setnsProcess) start() (err error) { default: return newSystemError(fmt.Errorf("invalid JSON payload from child")) } - return nil }) if err := syscall.Shutdown(int(p.parentPipe.Fd()), syscall.SHUT_WR); err != nil { @@ -264,7 +244,7 @@ func (p *initProcess) start() error { return newSystemErrorWithCause(err, "starting init process command") } if _, err := io.Copy(p.parentPipe, p.bootstrapData); err != nil { - return err + return newSystemErrorWithCause(err, "copying bootstrap data to pipe") } if err := p.execSetns(); err != nil { return newSystemErrorWithCause(err, "running exec setns process for init") @@ -301,25 +281,6 @@ func (p *initProcess) start() error { ierr := parseSync(p.parentPipe, func(sync *syncT) error { switch sync.Type { - case procConsole: - if err := writeSync(p.parentPipe, procConsoleReq); err != nil { - return newSystemErrorWithCause(err, "writing syncT 'request fd'") - } - - masterFile, err := utils.RecvFd(p.parentPipe) - if err != nil { - return newSystemErrorWithCause(err, "getting master pty from child pipe") - } - - if p.process.consoleChan == nil { - // TODO: Don't panic here, do something more sane. - panic("consoleChan is nil") - } - p.process.consoleChan <- masterFile - - if err := writeSync(p.parentPipe, procConsoleAck); err != nil { - return newSystemErrorWithCause(err, "writing syncT 'ack fd'") - } case procReady: if err := p.manager.Set(p.config.Config); err != nil { return newSystemErrorWithCause(err, "setting cgroup config for ready process") diff --git a/libcontainer/setns_init_linux.go b/libcontainer/setns_init_linux.go index 2e80450d..f6e8998b 100644 --- a/libcontainer/setns_init_linux.go +++ b/libcontainer/setns_init_linux.go @@ -16,8 +16,9 @@ import ( // linuxSetnsInit performs the container's initialization for running a new process // inside an existing container. type linuxSetnsInit struct { - pipe *os.File - config *initConfig + pipe *os.File + consoleSocket *os.File + config *initConfig } func (l *linuxSetnsInit) getSessionRingName() string { @@ -32,7 +33,7 @@ func (l *linuxSetnsInit) Init() error { } } if l.config.CreateConsole { - if err := setupConsole(l.pipe, l.config, false); err != nil { + if err := setupConsole(l.consoleSocket, l.config, false); err != nil { return err } if err := system.Setctty(); err != nil { diff --git a/libcontainer/standard_init_linux.go b/libcontainer/standard_init_linux.go index 7b68458e..d9cc5e50 100644 --- a/libcontainer/standard_init_linux.go +++ b/libcontainer/standard_init_linux.go @@ -17,10 +17,11 @@ import ( ) type linuxStandardInit struct { - pipe *os.File - parentPid int - stateDirFD int - config *initConfig + pipe *os.File + consoleSocket *os.File + parentPid int + stateDirFD int + config *initConfig } func (l *linuxStandardInit) getSessionRingParams() (string, uint32, uint32) { @@ -78,7 +79,7 @@ func (l *linuxStandardInit) Init() error { // but *after* we've given the user the chance to set up all of the mounts // they wanted. if l.config.CreateConsole { - if err := setupConsole(l.pipe, l.config, true); err != nil { + if err := setupConsole(l.consoleSocket, l.config, true); err != nil { return err } if err := system.Setctty(); err != nil { diff --git a/libcontainer/sync.go b/libcontainer/sync.go index 4016cd0d..cf7b45bc 100644 --- a/libcontainer/sync.go +++ b/libcontainer/sync.go @@ -27,14 +27,11 @@ type syncType string // procReady --> [final setup] // <-- procRun const ( - procError syncType = "procError" - procReady syncType = "procReady" - procRun syncType = "procRun" - procHooks syncType = "procHooks" - procResume syncType = "procResume" - procConsole syncType = "procConsole" - procConsoleReq syncType = "procConsoleReq" - procConsoleAck syncType = "procConsoleAck" + procError syncType = "procError" + procReady syncType = "procReady" + procRun syncType = "procRun" + procHooks syncType = "procHooks" + procResume syncType = "procResume" ) type syncT struct { diff --git a/libcontainer/utils/utils_unix.go b/libcontainer/utils/utils_unix.go index 408918f2..7b798cc7 100644 --- a/libcontainer/utils/utils_unix.go +++ b/libcontainer/utils/utils_unix.go @@ -4,6 +4,7 @@ package utils import ( "io/ioutil" + "os" "strconv" "syscall" ) @@ -31,3 +32,12 @@ func CloseExecFrom(minFd int) error { } return nil } + +// NewSockPair returns a new unix socket pair +func NewSockPair(name string) (parent *os.File, child *os.File, err error) { + fds, err := syscall.Socketpair(syscall.AF_LOCAL, syscall.SOCK_STREAM|syscall.SOCK_CLOEXEC, 0) + if err != nil { + return nil, nil, err + } + return os.NewFile(uintptr(fds[1]), name+"-p"), os.NewFile(uintptr(fds[0]), name+"-c"), nil +} diff --git a/restore.go b/restore.go index 561fb728..afc60465 100644 --- a/restore.go +++ b/restore.go @@ -161,7 +161,7 @@ func restoreContainer(context *cli.Context, spec *specs.Spec, config *configs.Co defer destroy(container) } process := &libcontainer.Process{} - tty, err := setupIO(process, rootuid, rootgid, false, detach) + tty, err := setupIO(process, rootuid, rootgid, false, detach, "") if err != nil { return -1, err } diff --git a/tty.go b/tty.go index 3bbbab48..9824df14 100644 --- a/tty.go +++ b/tty.go @@ -19,6 +19,7 @@ type tty struct { closers []io.Closer postStart []io.Closer wg sync.WaitGroup + consoleC chan error } func (t *tty) copyIO(w io.Writer, r io.ReadCloser) { @@ -68,37 +69,30 @@ func inheritStdio(process *libcontainer.Process) error { return nil } -func (t *tty) recvtty(process *libcontainer.Process, detach bool) error { - console, err := process.GetConsole() +func (t *tty) recvtty(process *libcontainer.Process, socket *os.File) error { + f, err := utils.RecvFd(socket) if err != nil { return err } - - if !detach { - go io.Copy(console, os.Stdin) - t.wg.Add(1) - go t.copyIO(os.Stdout, console) - - state, err := term.SetRawTerminal(os.Stdin.Fd()) - if err != nil { - return fmt.Errorf("failed to set the terminal from the stdin: %v", err) - } - t.state = state + console := libcontainer.ConsoleFromFile(f) + go io.Copy(console, os.Stdin) + t.wg.Add(1) + go t.copyIO(os.Stdout, console) + state, err := term.SetRawTerminal(os.Stdin.Fd()) + if err != nil { + return fmt.Errorf("failed to set the terminal from the stdin: %v", err) } - + t.state = state t.console = console t.closers = []io.Closer{console} return nil } -func (t *tty) sendtty(socket *os.File, ti *libcontainer.TerminalInfo) error { - if t.console == nil { - return fmt.Errorf("tty.console not set") +func (t *tty) waitConsole() error { + if t.consoleC != nil { + return <-t.consoleC } - - // Create a fake file to contain the terminal info. - console := os.NewFile(t.console.File().Fd(), ti.String()) - return utils.SendFd(socket, console) + return nil } // ClosePostStart closes any fds that are provided to the container and dup2'd diff --git a/utils_linux.go b/utils_linux.go index 29c45a30..230578c8 100644 --- a/utils_linux.go +++ b/utils_linux.go @@ -17,6 +17,7 @@ import ( "github.com/opencontainers/runc/libcontainer/cgroups/systemd" "github.com/opencontainers/runc/libcontainer/configs" "github.com/opencontainers/runc/libcontainer/specconv" + "github.com/opencontainers/runc/libcontainer/utils" "github.com/opencontainers/runtime-spec/specs-go" "github.com/urfave/cli" ) @@ -110,13 +111,45 @@ func destroy(container libcontainer.Container) { } // setupIO modifies the given process config according to the options. -func setupIO(process *libcontainer.Process, rootuid, rootgid int, createTTY, detach bool) (*tty, error) { - // This is entirely handled by recvtty. +func setupIO(process *libcontainer.Process, rootuid, rootgid int, createTTY, detach bool, sockpath string) (*tty, error) { if createTTY { process.Stdin = nil process.Stdout = nil process.Stderr = nil - return &tty{}, nil + t := &tty{} + if !detach { + parent, child, err := utils.NewSockPair("console") + if err != nil { + return nil, err + } + process.ConsoleSocket = child + t.postStart = append(t.postStart, parent, child) + t.consoleC = make(chan error, 1) + go func() { + if err := t.recvtty(process, parent); err != nil { + t.consoleC <- err + } + t.consoleC <- nil + }() + } else { + // the caller of runc will handle receiving the console master + conn, err := net.Dial("unix", sockpath) + if err != nil { + return nil, err + } + uc, ok := conn.(*net.UnixConn) + if !ok { + return nil, fmt.Errorf("casting to UnixConn failed") + } + t.postStart = append(t.postStart, uc) + socket, err := uc.File() + if err != nil { + return nil, err + } + t.postStart = append(t.postStart, socket) + process.ConsoleSocket = socket + } + return t, nil } // when runc will detach the caller provides the stdio to runc via runc's 0,1,2 // and the container's process inherits runc's stdio. @@ -190,48 +223,37 @@ func (r *runner) terminalinfo() *libcontainer.TerminalInfo { } func (r *runner) run(config *specs.Process) (int, error) { + if err := r.checkTerminal(config); err != nil { + r.destroy() + return -1, err + } process, err := newProcess(*config) if err != nil { r.destroy() return -1, err } - if len(r.listenFDs) > 0 { process.Env = append(process.Env, fmt.Sprintf("LISTEN_FDS=%d", len(r.listenFDs)), "LISTEN_PID=1") process.ExtraFiles = append(process.ExtraFiles, r.listenFDs...) } - baseFd := 3 + len(process.ExtraFiles) for i := baseFd; i < baseFd+r.preserveFDs; i++ { process.ExtraFiles = append(process.ExtraFiles, os.NewFile(uintptr(i), "PreserveFD:"+strconv.Itoa(i))) } - rootuid, err := r.container.Config().HostUID() if err != nil { r.destroy() return -1, err } - rootgid, err := r.container.Config().HostGID() if err != nil { r.destroy() return -1, err } - - detach := r.detach || r.create - - // Check command-line for sanity. - if detach && config.Terminal && r.consoleSocket == "" { - r.destroy() - return -1, fmt.Errorf("cannot allocate tty if runc will detach without setting console socket") - } - // XXX: Should we change this? - if (!detach || !config.Terminal) && r.consoleSocket != "" { - r.destroy() - return -1, fmt.Errorf("cannot use console socket if runc will not detach or allocate tty") - } - - startFn := r.container.Start + var ( + detach = r.detach || r.create + startFn = r.container.Start + ) if !r.create { startFn = r.container.Run } @@ -239,7 +261,7 @@ func (r *runner) run(config *specs.Process) (int, error) { // with detaching containers, and then we get a tty after the container has // started. handler := newSignalHandler(r.enableSubreaper, r.notifySocket) - tty, err := setupIO(process, rootuid, rootgid, config.Terminal, detach) + tty, err := setupIO(process, rootuid, rootgid, config.Terminal, detach, r.consoleSocket) if err != nil { r.destroy() return -1, err @@ -249,46 +271,11 @@ func (r *runner) run(config *specs.Process) (int, error) { r.destroy() return -1, err } - if config.Terminal { - if err = tty.recvtty(process, r.detach || r.create); err != nil { - r.terminate(process) - r.destroy() - return -1, err - } + if err := tty.waitConsole(); err != nil { + r.terminate(process) + r.destroy() + return -1, err } - - if config.Terminal && detach { - conn, err := net.Dial("unix", r.consoleSocket) - if err != nil { - r.terminate(process) - r.destroy() - return -1, err - } - defer conn.Close() - - unixconn, ok := conn.(*net.UnixConn) - if !ok { - r.terminate(process) - r.destroy() - return -1, fmt.Errorf("casting to UnixConn failed") - } - - socket, err := unixconn.File() - if err != nil { - r.terminate(process) - r.destroy() - return -1, err - } - defer socket.Close() - - err = tty.sendtty(socket, r.terminalinfo()) - if err != nil { - r.terminate(process) - r.destroy() - return -1, err - } - } - if err = tty.ClosePostStart(); err != nil { r.terminate(process) r.destroy() @@ -323,6 +310,18 @@ func (r *runner) terminate(p *libcontainer.Process) { _, _ = p.Wait() } +func (r *runner) checkTerminal(config *specs.Process) error { + detach := r.detach || r.create + // Check command-line for sanity. + if detach && config.Terminal && r.consoleSocket == "" { + return fmt.Errorf("cannot allocate tty if runc will detach without setting console socket") + } + if (!detach || !config.Terminal) && r.consoleSocket != "" { + return fmt.Errorf("cannot use console socket if runc will not detach or allocate tty") + } + return nil +} + func validateProcessSpec(spec *specs.Process) error { if spec.Cwd == "" { return fmt.Errorf("Cwd property must not be empty")