github.com/docker/docker@v299999999.0.0-20200612211812-aaf470eca7b5+incompatible/container/stream/attach.go (about)

     1  package stream // import "github.com/docker/docker/container/stream"
     2  
     3  import (
     4  	"context"
     5  	"io"
     6  
     7  	"github.com/docker/docker/pkg/pools"
     8  	"github.com/moby/term"
     9  	"github.com/pkg/errors"
    10  	"github.com/sirupsen/logrus"
    11  	"golang.org/x/sync/errgroup"
    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 group errgroup.Group
    62  
    63  	// Connect stdin of container to the attach stdin stream.
    64  	if cfg.Stdin != nil {
    65  		group.Go(func() error {
    66  			logrus.Debug("attach: stdin: begin")
    67  			defer logrus.Debug("attach: stdin: end")
    68  
    69  			defer func() {
    70  				if cfg.CloseStdin && !cfg.TTY {
    71  					cfg.CStdin.Close()
    72  				} else {
    73  					// No matter what, when stdin is closed (io.Copy unblock), close stdout and stderr
    74  					if cfg.CStdout != nil {
    75  						cfg.CStdout.Close()
    76  					}
    77  					if cfg.CStderr != nil {
    78  						cfg.CStderr.Close()
    79  					}
    80  				}
    81  			}()
    82  
    83  			var err error
    84  			if cfg.TTY {
    85  				_, err = copyEscapable(cfg.CStdin, cfg.Stdin, cfg.DetachKeys)
    86  			} else {
    87  				_, err = pools.Copy(cfg.CStdin, cfg.Stdin)
    88  			}
    89  			if err == io.ErrClosedPipe {
    90  				err = nil
    91  			}
    92  			if err != nil {
    93  				logrus.WithError(err).Debug("error on attach stdin")
    94  				return errors.Wrap(err, "error on attach stdin")
    95  			}
    96  			return nil
    97  		})
    98  	}
    99  
   100  	attachStream := func(name string, stream io.Writer, streamPipe io.ReadCloser) error {
   101  		logrus.Debugf("attach: %s: begin", name)
   102  		defer logrus.Debugf("attach: %s: end", name)
   103  		defer func() {
   104  			// Make sure stdin gets closed
   105  			if cfg.Stdin != nil {
   106  				cfg.Stdin.Close()
   107  			}
   108  			streamPipe.Close()
   109  		}()
   110  
   111  		_, err := pools.Copy(stream, streamPipe)
   112  		if err == io.ErrClosedPipe {
   113  			err = nil
   114  		}
   115  		if err != nil {
   116  			logrus.WithError(err).Debugf("attach: %s", name)
   117  			return errors.Wrapf(err, "error attaching %s stream", name)
   118  		}
   119  		return nil
   120  	}
   121  
   122  	if cfg.Stdout != nil {
   123  		group.Go(func() error {
   124  			return attachStream("stdout", cfg.Stdout, cfg.CStdout)
   125  		})
   126  	}
   127  	if cfg.Stderr != nil {
   128  		group.Go(func() error {
   129  			return attachStream("stderr", cfg.Stderr, cfg.CStderr)
   130  		})
   131  	}
   132  
   133  	errs := make(chan error, 1)
   134  	go func() {
   135  		defer logrus.Debug("attach done")
   136  		groupErr := make(chan error, 1)
   137  		go func() {
   138  			groupErr <- group.Wait()
   139  		}()
   140  		select {
   141  		case <-ctx.Done():
   142  			// close all pipes
   143  			if cfg.CStdin != nil {
   144  				cfg.CStdin.Close()
   145  			}
   146  			if cfg.CStdout != nil {
   147  				cfg.CStdout.Close()
   148  			}
   149  			if cfg.CStderr != nil {
   150  				cfg.CStderr.Close()
   151  			}
   152  
   153  			// Now with these closed, wait should return.
   154  			if err := group.Wait(); err != nil {
   155  				errs <- err
   156  				return
   157  			}
   158  			errs <- ctx.Err()
   159  		case err := <-groupErr:
   160  			errs <- err
   161  		}
   162  	}()
   163  
   164  	return errs
   165  }
   166  
   167  func copyEscapable(dst io.Writer, src io.ReadCloser, keys []byte) (written int64, err error) {
   168  	if len(keys) == 0 {
   169  		keys = defaultEscapeSequence
   170  	}
   171  	pr := term.NewEscapeProxy(src, keys)
   172  	defer src.Close()
   173  
   174  	return pools.Copy(dst, pr)
   175  }