func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, stdout io.Writer, stderr io.Writer) chan error { var cStdout, cStderr io.ReadCloser var 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() { utils.Debugf("attach: stdin: begin") defer utils.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 { utils.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() { utils.Debugf("attach: stdout: begin") defer utils.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 { utils.Errorf("attach: stdout: %s", err) } errors <- err }() } } else { go func() { if stdinCloser != nil { defer stdinCloser.Close() } if cStdout, err := container.StdoutPipe(); err != nil { utils.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() { utils.Debugf("attach: stderr: begin") defer utils.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 { utils.Errorf("attach: stderr: %s", err) } errors <- err }() } } else { go func() { if stdinCloser != nil { defer stdinCloser.Close() } if cStderr, err := container.StderrPipe(); err != nil { utils.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 { utils.Debugf("attach: waiting for job %d/%d", i+1, nJobs) if err := <-errors; err != nil { utils.Errorf("attach: job %d returned error %s, aborting all jobs", i+1, err) return err } utils.Debugf("attach: job %d completed successfully", i+1) } utils.Debugf("attach: all jobs completed successfully") return nil }) }
func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, stdout io.Writer, stderr io.Writer) chan error { var cStdout, cStderr io.ReadCloser var 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() { utils.Debugf("[start] attach stdin\n") defer utils.Debugf("[end] attach stdin\n") // No matter what, when stdin is closed (io.Copy unblock), close stdout and stderr if cStdout != nil { defer cStdout.Close() } if cStderr != nil { defer cStderr.Close() } if container.Config.StdinOnce && !container.Config.Tty { defer cStdin.Close() } if container.Config.Tty { _, err = utils.CopyEscapable(cStdin, stdin) } else { _, err = io.Copy(cStdin, stdin) } if err != nil { utils.Debugf("[error] attach stdin: %s\n", err) } // Discard error, expecting pipe error errors <- nil }() } } if stdout != nil { nJobs += 1 if p, err := container.StdoutPipe(); err != nil { errors <- err } else { cStdout = p go func() { utils.Debugf("[start] attach stdout\n") defer utils.Debugf("[end] attach stdout\n") // If we are in StdinOnce mode, then close stdin if container.Config.StdinOnce { if stdin != nil { defer stdin.Close() } if stdinCloser != nil { defer stdinCloser.Close() } } _, err := io.Copy(stdout, cStdout) if err != nil { utils.Debugf("[error] attach stdout: %s\n", err) } errors <- err }() } } if stderr != nil { nJobs += 1 if p, err := container.StderrPipe(); err != nil { errors <- err } else { cStderr = p go func() { utils.Debugf("[start] attach stderr\n") defer utils.Debugf("[end] attach stderr\n") // If we are in StdinOnce mode, then close stdin if container.Config.StdinOnce { if stdin != nil { defer stdin.Close() } if stdinCloser != nil { defer stdinCloser.Close() } } _, err := io.Copy(stderr, cStderr) if err != nil { utils.Debugf("[error] attach stderr: %s\n", err) } errors <- err }() } } return utils.Go(func() error { if cStdout != nil { defer cStdout.Close() } if cStderr != nil { defer cStderr.Close() } // FIXME: how do 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 { utils.Debugf("Waiting for job %d/%d\n", i+1, nJobs) if err := <-errors; err != nil { utils.Debugf("Job %d returned error %s. Aborting all jobs\n", i+1, err) return err } utils.Debugf("Job %d completed successfully\n", i+1) } utils.Debugf("All jobs completed successfully\n") return nil }) }