github.com/lazyboychen7/engine@v17.12.1-ce-rc2+incompatible/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/docker/docker/pkg/pools"
    10  	"github.com/docker/docker/pkg/term"
    11  	"github.com/sirupsen/logrus"
    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 = pools.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 := pools.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  	errs := make(chan error, 1)
   140  
   141  	go func() {
   142  		defer close(errs)
   143  		errs <- func() error {
   144  			done := make(chan struct{})
   145  			go func() {
   146  				wg.Wait()
   147  				close(done)
   148  			}()
   149  			select {
   150  			case <-done:
   151  			case <-ctx.Done():
   152  				// close all pipes
   153  				if cfg.CStdin != nil {
   154  					cfg.CStdin.Close()
   155  				}
   156  				if cfg.CStdout != nil {
   157  					cfg.CStdout.Close()
   158  				}
   159  				if cfg.CStderr != nil {
   160  					cfg.CStderr.Close()
   161  				}
   162  				<-done
   163  			}
   164  			close(errors)
   165  			for err := range errors {
   166  				if err != nil {
   167  					return err
   168  				}
   169  			}
   170  			return nil
   171  		}()
   172  	}()
   173  
   174  	return errs
   175  }
   176  
   177  func copyEscapable(dst io.Writer, src io.ReadCloser, keys []byte) (written int64, err error) {
   178  	if len(keys) == 0 {
   179  		keys = defaultEscapeSequence
   180  	}
   181  	pr := term.NewEscapeProxy(src, keys)
   182  	defer src.Close()
   183  
   184  	return pools.Copy(dst, pr)
   185  }