github.com/jiasir/docker@v1.3.3-0.20170609024000-252e610103e7/container/stream/attach.go (about)

     1  package stream
     2  
     3  import (
     4  	"io"
     5  	"sync"
     6  
     7  	"golang.org/x/net/context"
     8  
     9  	"github.com/Sirupsen/logrus"
    10  	"github.com/docker/docker/pkg/promise"
    11  	"github.com/docker/docker/pkg/term"
    12  )
    13  
    14  var defaultEscapeSequence = []byte{16, 17} // ctrl-p, ctrl-q
    15  
    16  // AttachConfig is the config struct used to attach a client to a stream's stdio
    17  type AttachConfig struct {
    18  	// Tells the attach copier that the stream's stdin is a TTY and to look for
    19  	// escape sequences in stdin to detach from the stream.
    20  	// When true the escape sequence is not passed to the underlying stream
    21  	TTY bool
    22  	// Specifies the detach keys the client will be using
    23  	// Only useful when `TTY` is true
    24  	DetachKeys []byte
    25  
    26  	// CloseStdin signals that once done, stdin for the attached stream should be closed
    27  	// For example, this would close the attached container's stdin.
    28  	CloseStdin bool
    29  
    30  	// UseStd* indicate whether the client has requested to be connected to the
    31  	// given stream or not.  These flags are used instead of checking Std* != nil
    32  	// at points before the client streams Std* are wired up.
    33  	UseStdin, UseStdout, UseStderr bool
    34  
    35  	// CStd* are the streams directly connected to the container
    36  	CStdin           io.WriteCloser
    37  	CStdout, CStderr io.ReadCloser
    38  
    39  	// Provide client streams to wire up to
    40  	Stdin          io.ReadCloser
    41  	Stdout, Stderr io.Writer
    42  }
    43  
    44  // AttachStreams attaches the container's streams to the AttachConfig
    45  func (c *Config) AttachStreams(cfg *AttachConfig) {
    46  	if cfg.UseStdin {
    47  		cfg.CStdin = c.StdinPipe()
    48  	}
    49  
    50  	if cfg.UseStdout {
    51  		cfg.CStdout = c.StdoutPipe()
    52  	}
    53  
    54  	if cfg.UseStderr {
    55  		cfg.CStderr = c.StderrPipe()
    56  	}
    57  }
    58  
    59  // CopyStreams starts goroutines to copy data in and out to/from the container
    60  func (c *Config) CopyStreams(ctx context.Context, cfg *AttachConfig) chan error {
    61  	var (
    62  		wg     sync.WaitGroup
    63  		errors = make(chan error, 3)
    64  	)
    65  
    66  	if cfg.Stdin != nil {
    67  		wg.Add(1)
    68  	}
    69  
    70  	if cfg.Stdout != nil {
    71  		wg.Add(1)
    72  	}
    73  
    74  	if cfg.Stderr != nil {
    75  		wg.Add(1)
    76  	}
    77  
    78  	// Connect stdin of container to the attach stdin stream.
    79  	go func() {
    80  		if cfg.Stdin == nil {
    81  			return
    82  		}
    83  		logrus.Debug("attach: stdin: begin")
    84  
    85  		var err error
    86  		if cfg.TTY {
    87  			_, err = copyEscapable(cfg.CStdin, cfg.Stdin, cfg.DetachKeys)
    88  		} else {
    89  			_, err = io.Copy(cfg.CStdin, cfg.Stdin)
    90  		}
    91  		if err == io.ErrClosedPipe {
    92  			err = nil
    93  		}
    94  		if err != nil {
    95  			logrus.Errorf("attach: stdin: %s", err)
    96  			errors <- err
    97  		}
    98  		if cfg.CloseStdin && !cfg.TTY {
    99  			cfg.CStdin.Close()
   100  		} else {
   101  			// No matter what, when stdin is closed (io.Copy unblock), close stdout and stderr
   102  			if cfg.CStdout != nil {
   103  				cfg.CStdout.Close()
   104  			}
   105  			if cfg.CStderr != nil {
   106  				cfg.CStderr.Close()
   107  			}
   108  		}
   109  		logrus.Debug("attach: stdin: end")
   110  		wg.Done()
   111  	}()
   112  
   113  	attachStream := func(name string, stream io.Writer, streamPipe io.ReadCloser) {
   114  		if stream == nil {
   115  			return
   116  		}
   117  
   118  		logrus.Debugf("attach: %s: begin", name)
   119  		_, err := io.Copy(stream, streamPipe)
   120  		if err == io.ErrClosedPipe {
   121  			err = nil
   122  		}
   123  		if err != nil {
   124  			logrus.Errorf("attach: %s: %v", name, err)
   125  			errors <- err
   126  		}
   127  		// Make sure stdin gets closed
   128  		if cfg.Stdin != nil {
   129  			cfg.Stdin.Close()
   130  		}
   131  		streamPipe.Close()
   132  		logrus.Debugf("attach: %s: end", name)
   133  		wg.Done()
   134  	}
   135  
   136  	go attachStream("stdout", cfg.Stdout, cfg.CStdout)
   137  	go attachStream("stderr", cfg.Stderr, cfg.CStderr)
   138  
   139  	return promise.Go(func() error {
   140  		done := make(chan struct{})
   141  		go func() {
   142  			wg.Wait()
   143  			close(done)
   144  		}()
   145  		select {
   146  		case <-done:
   147  		case <-ctx.Done():
   148  			// close all pipes
   149  			if cfg.CStdin != nil {
   150  				cfg.CStdin.Close()
   151  			}
   152  			if cfg.CStdout != nil {
   153  				cfg.CStdout.Close()
   154  			}
   155  			if cfg.CStderr != nil {
   156  				cfg.CStderr.Close()
   157  			}
   158  			<-done
   159  		}
   160  		close(errors)
   161  		for err := range errors {
   162  			if err != nil {
   163  				return err
   164  			}
   165  		}
   166  		return nil
   167  	})
   168  }
   169  
   170  func copyEscapable(dst io.Writer, src io.ReadCloser, keys []byte) (written int64, err error) {
   171  	if len(keys) == 0 {
   172  		keys = defaultEscapeSequence
   173  	}
   174  	pr := term.NewEscapeProxy(src, keys)
   175  	defer src.Close()
   176  
   177  	return io.Copy(dst, pr)
   178  }