// AttachStreams is called by libcontainerd to connect the stdio. func (daemon *Daemon) AttachStreams(id string, iop libcontainerd.IOPipe) error { var ( s *runconfig.StreamConfig ec *exec.Config ) c := daemon.containers.Get(id) if c == nil { var err error ec, err = daemon.getExecConfig(id) if err != nil { return fmt.Errorf("no such exec/container: %s", id) } s = ec.StreamConfig } else { s = c.StreamConfig if err := daemon.StartLogging(c); err != nil { c.Reset(false) return err } } copyFunc := func(w io.Writer, r io.Reader) { s.Add(1) go func() { if _, err := io.Copy(w, r); err != nil { logrus.Errorf("%v stream copy error: %v", id, err) } s.Done() }() } if iop.Stdout != nil { copyFunc(s.Stdout(), iop.Stdout) } if iop.Stderr != nil { copyFunc(s.Stderr(), iop.Stderr) } if stdin := s.Stdin(); stdin != nil { if iop.Stdin != nil { go func() { io.Copy(iop.Stdin, stdin) iop.Stdin.Close() }() } } else { //TODO(swernli): On Windows, not closing stdin when no tty is requested by the exec Config // results in a hang. We should re-evaluate generalizing this fix for all OSes if // we can determine that is the right thing to do more generally. if (c != nil && !c.Config.Tty) || (ec != nil && !ec.Tty && runtime.GOOS == "windows") { // tty is enabled, so dont close containerd's iopipe stdin. if iop.Stdin != nil { iop.Stdin.Close() } } } return nil }
// AttachStreams connects streams to a TTY. // Used by exec too. Should this move somewhere else? func AttachStreams(ctx context.Context, streamConfig *runconfig.StreamConfig, openStdin, stdinOnce, tty bool, stdin io.ReadCloser, stdout io.Writer, stderr io.Writer, keys []byte) chan error { var ( cStdout, cStderr io.ReadCloser cStdin io.WriteCloser wg sync.WaitGroup errors = make(chan error, 3) ) if stdin != nil && openStdin { cStdin = streamConfig.StdinPipe() wg.Add(1) } if stdout != nil { cStdout = streamConfig.StdoutPipe() wg.Add(1) } if stderr != nil { cStderr = streamConfig.StderrPipe() wg.Add(1) } // Connect stdin of container to the http conn. go func() { if stdin == nil || !openStdin { return } logrus.Debug("attach: stdin: begin") var err error if tty { _, err = copyEscapable(cStdin, stdin, keys) } else { _, err = io.Copy(cStdin, stdin) } if err == io.ErrClosedPipe { err = nil } if err != nil { logrus.Errorf("attach: stdin: %s", err) errors <- err } if stdinOnce && !tty { cStdin.Close() } else { // No matter what, when stdin is closed (io.Copy unblock), close stdout and stderr if cStdout != nil { cStdout.Close() } if cStderr != nil { cStderr.Close() } } logrus.Debug("attach: stdin: end") wg.Done() }() attachStream := func(name string, stream io.Writer, streamPipe io.ReadCloser) { if stream == nil { return } logrus.Debugf("attach: %s: begin", name) _, err := io.Copy(stream, streamPipe) if err == io.ErrClosedPipe { err = nil } if err != nil { logrus.Errorf("attach: %s: %v", name, err) errors <- err } // Make sure stdin gets closed if stdin != nil { stdin.Close() } streamPipe.Close() logrus.Debugf("attach: %s: end", name) wg.Done() } go attachStream("stdout", stdout, cStdout) go attachStream("stderr", stderr, cStderr) return promise.Go(func() error { done := make(chan struct{}) go func() { wg.Wait() close(done) }() select { case <-done: case <-ctx.Done(): // close all pipes if cStdin != nil { cStdin.Close() } if cStdout != nil { cStdout.Close() } if cStderr != nil { cStderr.Close() } <-done } close(errors) for err := range errors { if err != nil { return err } } return nil }) }
// AttachStreams is called by libcontainerd to connect the stdio. func (daemon *Daemon) AttachStreams(id string, iop libcontainerd.IOPipe) error { var s *runconfig.StreamConfig c := daemon.containers.Get(id) if c == nil { ec, err := daemon.getExecConfig(id) if err != nil { return fmt.Errorf("no such exec/container: %s", id) } s = ec.StreamConfig } else { s = c.StreamConfig if err := daemon.StartLogging(c); err != nil { c.Reset(false) return err } } if stdin := s.Stdin(); stdin != nil { if iop.Stdin != nil { go func() { io.Copy(iop.Stdin, stdin) iop.Stdin.Close() }() } } else { if c != nil && !c.Config.Tty { // tty is enabled, so dont close containerd's iopipe stdin. if iop.Stdin != nil { iop.Stdin.Close() } } } copy := func(w io.Writer, r io.Reader) { s.Add(1) go func() { if _, err := io.Copy(w, r); err != nil { logrus.Errorf("%v stream copy error: %v", id, err) } s.Done() }() } if iop.Stdout != nil { copy(s.Stdout(), iop.Stdout) } if iop.Stderr != nil { copy(s.Stderr(), iop.Stderr) } return nil }