// FIXME: this should be private, and every outside subsystem // should go through the "container_attach" job. But that would require // that job to be properly documented, as well as the relationship betweem // Attach and ContainerAttach. // // This method is in use by builder/builder.go. func (daemon *Daemon) Attach(container *Container, stdin io.ReadCloser, stdinCloser io.Closer, stdout io.Writer, stderr io.Writer) chan error { var ( cStdout, cStderr io.ReadCloser nJobs int errors = make(chan error, 3) ) if stdin != nil && container.Config.OpenStdin { nJobs += 1 if cStdin, err := container.StdinPipe(); err != nil { errors <- err } else { go func() { log.Debugf("attach: stdin: begin") defer log.Debugf("attach: stdin: end") // No matter what, when stdin is closed (io.Copy unblock), close stdout and stderr if container.Config.StdinOnce && !container.Config.Tty { defer cStdin.Close() } else { defer func() { if cStdout != nil { cStdout.Close() } if cStderr != nil { cStderr.Close() } }() } if container.Config.Tty { _, err = utils.CopyEscapable(cStdin, stdin) } else { _, err = io.Copy(cStdin, stdin) } if err == io.ErrClosedPipe { err = nil } if err != nil { log.Errorf("attach: stdin: %s", err) } errors <- err }() } } if stdout != nil { nJobs += 1 if p, err := container.StdoutPipe(); err != nil { errors <- err } else { cStdout = p go func() { log.Debugf("attach: stdout: begin") defer log.Debugf("attach: stdout: end") // If we are in StdinOnce mode, then close stdin if container.Config.StdinOnce && stdin != nil { defer stdin.Close() } if stdinCloser != nil { defer stdinCloser.Close() } _, err := io.Copy(stdout, cStdout) if err == io.ErrClosedPipe { err = nil } if err != nil { log.Errorf("attach: stdout: %s", err) } errors <- err }() } } else { go func() { if stdinCloser != nil { defer stdinCloser.Close() } if cStdout, err := container.StdoutPipe(); err != nil { log.Errorf("attach: stdout pipe: %s", err) } else { io.Copy(&utils.NopWriter{}, cStdout) } }() } if stderr != nil { nJobs += 1 if p, err := container.StderrPipe(); err != nil { errors <- err } else { cStderr = p go func() { log.Debugf("attach: stderr: begin") defer log.Debugf("attach: stderr: end") // If we are in StdinOnce mode, then close stdin if container.Config.StdinOnce && stdin != nil { defer stdin.Close() } if stdinCloser != nil { defer stdinCloser.Close() } _, err := io.Copy(stderr, cStderr) if err == io.ErrClosedPipe { err = nil } if err != nil { log.Errorf("attach: stderr: %s", err) } errors <- err }() } } else { go func() { if stdinCloser != nil { defer stdinCloser.Close() } if cStderr, err := container.StderrPipe(); err != nil { log.Errorf("attach: stdout pipe: %s", err) } else { io.Copy(&utils.NopWriter{}, cStderr) } }() } return utils.Go(func() error { defer func() { if cStdout != nil { cStdout.Close() } if cStderr != nil { cStderr.Close() } }() // FIXME: how to clean up the stdin goroutine without the unwanted side effect // of closing the passed stdin? Add an intermediary io.Pipe? for i := 0; i < nJobs; i += 1 { log.Debugf("attach: waiting for job %d/%d", i+1, nJobs) if err := <-errors; err != nil { log.Errorf("attach: job %d returned error %s, aborting all jobs", i+1, err) return err } log.Debugf("attach: job %d completed successfully", i+1) } log.Debugf("attach: all jobs completed successfully") return nil }) }
// FIXME: this should be private, and every outside subsystem // should go through the "container_attach" job. But that would require // that job to be properly documented, as well as the relationship betweem // Attach and ContainerAttach. // // This method is in use by builder/builder.go. func (daemon *Daemon) Attach(streamConfig *StreamConfig, openStdin, stdinOnce, tty bool, stdin io.ReadCloser, stdinCloser io.Closer, stdout io.Writer, stderr io.Writer) chan error { var ( cStdout, cStderr io.ReadCloser nJobs int errors = make(chan error, 3) ) // Connect stdin of container to the http conn. if stdin != nil && openStdin { nJobs++ // Get the stdin pipe. if cStdin, err := streamConfig.StdinPipe(); err != nil { errors <- err } else { go func() { log.Debugf("attach: stdin: begin") defer log.Debugf("attach: stdin: end") // No matter what, when stdin is closed (io.Copy unblock), close stdout and stderr if stdinOnce && !tty { defer cStdin.Close() } else { defer func() { if cStdout != nil { cStdout.Close() } if cStderr != nil { cStderr.Close() } }() } if tty { _, err = utils.CopyEscapable(cStdin, stdin) } else { _, err = io.Copy(cStdin, stdin) } if err == io.ErrClosedPipe { err = nil } if err != nil { log.Errorf("attach: stdin: %s", err) } errors <- err }() } } if stdout != nil { nJobs++ // Get a reader end of a pipe that is attached as stdout to the container. if p, err := streamConfig.StdoutPipe(); err != nil { errors <- err } else { cStdout = p go func() { log.Debugf("attach: stdout: begin") defer log.Debugf("attach: stdout: end") // If we are in StdinOnce mode, then close stdin if stdinOnce && stdin != nil { defer stdin.Close() } if stdinCloser != nil { defer stdinCloser.Close() } _, err := io.Copy(stdout, cStdout) if err == io.ErrClosedPipe { err = nil } if err != nil { log.Errorf("attach: stdout: %s", err) } errors <- err }() } } else { // Point stdout of container to a no-op writer. go func() { if stdinCloser != nil { defer stdinCloser.Close() } if cStdout, err := streamConfig.StdoutPipe(); err != nil { log.Errorf("attach: stdout pipe: %s", err) } else { io.Copy(&ioutils.NopWriter{}, cStdout) } }() } if stderr != nil { nJobs++ if p, err := streamConfig.StderrPipe(); err != nil { errors <- err } else { cStderr = p go func() { log.Debugf("attach: stderr: begin") defer log.Debugf("attach: stderr: end") // If we are in StdinOnce mode, then close stdin // Why are we closing stdin here and above while handling stdout? if stdinOnce && stdin != nil { defer stdin.Close() } if stdinCloser != nil { defer stdinCloser.Close() } _, err := io.Copy(stderr, cStderr) if err == io.ErrClosedPipe { err = nil } if err != nil { log.Errorf("attach: stderr: %s", err) } errors <- err }() } } else { // Point stderr at a no-op writer. go func() { if stdinCloser != nil { defer stdinCloser.Close() } if cStderr, err := streamConfig.StderrPipe(); err != nil { log.Errorf("attach: stdout pipe: %s", err) } else { io.Copy(&ioutils.NopWriter{}, cStderr) } }() } return utils.Go(func() error { defer func() { if cStdout != nil { cStdout.Close() } if cStderr != nil { cStderr.Close() } }() // FIXME: how to clean up the stdin goroutine without the unwanted side effect // of closing the passed stdin? Add an intermediary io.Pipe? for i := 0; i < nJobs; i++ { log.Debugf("attach: waiting for job %d/%d", i+1, nJobs) if err := <-errors; err != nil { log.Errorf("attach: job %d returned error %s, aborting all jobs", i+1, err) return err } log.Debugf("attach: job %d completed successfully", i+1) } log.Debugf("attach: all jobs completed successfully") return nil }) }
func (daemon *Daemon) attach(streamConfig *StreamConfig, openStdin, stdinOnce, tty bool, stdin io.ReadCloser, stdout io.Writer, stderr io.Writer) 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 } log.Debugf("attach: stdin: begin") defer func() { 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() } } wg.Done() log.Debugf("attach: stdin: end") }() var err error if tty { _, err = utils.CopyEscapable(cStdin, stdin) } else { _, err = io.Copy(cStdin, stdin) } if err == io.ErrClosedPipe { err = nil } if err != nil { log.Errorf("attach: stdin: %s", err) errors <- err return } }() attachStream := func(name string, stream io.Writer, streamPipe io.ReadCloser) { if stream == nil { return } defer func() { // Make sure stdin gets closed if stdin != nil { stdin.Close() } streamPipe.Close() wg.Done() log.Debugf("attach: %s: end", name) }() log.Debugf("attach: %s: begin", name) _, err := io.Copy(stream, streamPipe) if err == io.ErrClosedPipe { err = nil } if err != nil { log.Errorf("attach: %s: %v", name, err) errors <- err } } go attachStream("stdout", stdout, cStdout) go attachStream("stderr", stderr, cStderr) return promise.Go(func() error { wg.Wait() close(errors) for err := range errors { if err != nil { return err } } return nil }) }