github.com/fabiokung/docker@v0.11.2-0.20170222101415-4534dcd49497/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  )
    12  
    13  var defaultEscapeSequence = []byte{16, 17} // ctrl-p, ctrl-q
    14  
    15  // DetachError is special error which returned in case of container detach.
    16  type DetachError struct{}
    17  
    18  func (DetachError) Error() string {
    19  	return "detached from container"
    20  }
    21  
    22  // AttachConfig is the config struct used to attach a client to a stream's stdio
    23  type AttachConfig struct {
    24  	// Tells the attach copier that the stream's stdin is a TTY and to look for
    25  	// escape sequences in stdin to detach from the stream.
    26  	// When true the escape sequence is not passed to the underlying stream
    27  	TTY bool
    28  	// Specifies the detach keys the client will be using
    29  	// Only useful when `TTY` is true
    30  	DetachKeys []byte
    31  
    32  	// CloseStdin signals that once done, stdin for the attached stream should be closed
    33  	// For example, this would close the attached container's stdin.
    34  	CloseStdin bool
    35  
    36  	// UseStd* indicate whether the client has requested to be connected to the
    37  	// given stream or not.  These flags are used instead of checking Std* != nil
    38  	// at points before the client streams Std* are wired up.
    39  	UseStdin, UseStdout, UseStderr bool
    40  
    41  	// CStd* are the streams directly connected to the container
    42  	CStdin           io.WriteCloser
    43  	CStdout, CStderr io.ReadCloser
    44  
    45  	// Provide client streams to wire up to
    46  	Stdin          io.ReadCloser
    47  	Stdout, Stderr io.Writer
    48  }
    49  
    50  // AttachStreams attaches the container's streams to the AttachConfig
    51  func (c *Config) AttachStreams(cfg *AttachConfig) {
    52  	if cfg.UseStdin {
    53  		cfg.CStdin = c.StdinPipe()
    54  	}
    55  
    56  	if cfg.UseStdout {
    57  		cfg.CStdout = c.StdoutPipe()
    58  	}
    59  
    60  	if cfg.UseStderr {
    61  		cfg.CStderr = c.StderrPipe()
    62  	}
    63  }
    64  
    65  // CopyStreams starts goroutines to copy data in and out to/from the container
    66  func (c *Config) CopyStreams(ctx context.Context, cfg *AttachConfig) chan error {
    67  	var (
    68  		wg     sync.WaitGroup
    69  		errors = make(chan error, 3)
    70  	)
    71  
    72  	if cfg.Stdin != nil {
    73  		wg.Add(1)
    74  	}
    75  
    76  	if cfg.Stdout != nil {
    77  		wg.Add(1)
    78  	}
    79  
    80  	if cfg.Stderr != nil {
    81  		wg.Add(1)
    82  	}
    83  
    84  	// Connect stdin of container to the attach stdin stream.
    85  	go func() {
    86  		if cfg.Stdin == nil {
    87  			return
    88  		}
    89  		logrus.Debug("attach: stdin: begin")
    90  
    91  		var err error
    92  		if cfg.TTY {
    93  			_, err = copyEscapable(cfg.CStdin, cfg.Stdin, cfg.DetachKeys)
    94  		} else {
    95  			_, err = io.Copy(cfg.CStdin, cfg.Stdin)
    96  		}
    97  		if err == io.ErrClosedPipe {
    98  			err = nil
    99  		}
   100  		if err != nil {
   101  			logrus.Errorf("attach: stdin: %s", err)
   102  			errors <- err
   103  		}
   104  		if cfg.CloseStdin && !cfg.TTY {
   105  			cfg.CStdin.Close()
   106  		} else {
   107  			// No matter what, when stdin is closed (io.Copy unblock), close stdout and stderr
   108  			if cfg.CStdout != nil {
   109  				cfg.CStdout.Close()
   110  			}
   111  			if cfg.CStderr != nil {
   112  				cfg.CStderr.Close()
   113  			}
   114  		}
   115  		logrus.Debug("attach: stdin: end")
   116  		wg.Done()
   117  	}()
   118  
   119  	attachStream := func(name string, stream io.Writer, streamPipe io.ReadCloser) {
   120  		if stream == nil {
   121  			return
   122  		}
   123  
   124  		logrus.Debugf("attach: %s: begin", name)
   125  		_, err := io.Copy(stream, streamPipe)
   126  		if err == io.ErrClosedPipe {
   127  			err = nil
   128  		}
   129  		if err != nil {
   130  			logrus.Errorf("attach: %s: %v", name, err)
   131  			errors <- err
   132  		}
   133  		// Make sure stdin gets closed
   134  		if cfg.Stdin != nil {
   135  			cfg.Stdin.Close()
   136  		}
   137  		streamPipe.Close()
   138  		logrus.Debugf("attach: %s: end", name)
   139  		wg.Done()
   140  	}
   141  
   142  	go attachStream("stdout", cfg.Stdout, cfg.CStdout)
   143  	go attachStream("stderr", cfg.Stderr, cfg.CStderr)
   144  
   145  	return promise.Go(func() error {
   146  		done := make(chan struct{})
   147  		go func() {
   148  			wg.Wait()
   149  			close(done)
   150  		}()
   151  		select {
   152  		case <-done:
   153  		case <-ctx.Done():
   154  			// close all pipes
   155  			if cfg.CStdin != nil {
   156  				cfg.CStdin.Close()
   157  			}
   158  			if cfg.CStdout != nil {
   159  				cfg.CStdout.Close()
   160  			}
   161  			if cfg.CStderr != nil {
   162  				cfg.CStderr.Close()
   163  			}
   164  			<-done
   165  		}
   166  		close(errors)
   167  		for err := range errors {
   168  			if err != nil {
   169  				return err
   170  			}
   171  		}
   172  		return nil
   173  	})
   174  }
   175  
   176  // ttyProxy is used only for attaches with a TTY. It is used to proxy
   177  // stdin keypresses from the underlying reader and look for the passed in
   178  // escape key sequence to signal a detach.
   179  type ttyProxy struct {
   180  	escapeKeys   []byte
   181  	escapeKeyPos int
   182  	r            io.Reader
   183  }
   184  
   185  func (r *ttyProxy) Read(buf []byte) (int, error) {
   186  	nr, err := r.r.Read(buf)
   187  
   188  	preserve := func() {
   189  		// this preserves the original key presses in the passed in buffer
   190  		nr += r.escapeKeyPos
   191  		preserve := make([]byte, 0, r.escapeKeyPos+len(buf))
   192  		preserve = append(preserve, r.escapeKeys[:r.escapeKeyPos]...)
   193  		preserve = append(preserve, buf...)
   194  		r.escapeKeyPos = 0
   195  		copy(buf[0:nr], preserve)
   196  	}
   197  
   198  	if nr != 1 || err != nil {
   199  		if r.escapeKeyPos > 0 {
   200  			preserve()
   201  		}
   202  		return nr, err
   203  	}
   204  
   205  	if buf[0] != r.escapeKeys[r.escapeKeyPos] {
   206  		if r.escapeKeyPos > 0 {
   207  			preserve()
   208  		}
   209  		return nr, nil
   210  	}
   211  
   212  	if r.escapeKeyPos == len(r.escapeKeys)-1 {
   213  		return 0, DetachError{}
   214  	}
   215  
   216  	// Looks like we've got an escape key, but we need to match again on the next
   217  	// read.
   218  	// Store the current escape key we found so we can look for the next one on
   219  	// the next read.
   220  	// Since this is an escape key, make sure we don't let the caller read it
   221  	// If later on we find that this is not the escape sequence, we'll add the
   222  	// keys back
   223  	r.escapeKeyPos++
   224  	return nr - r.escapeKeyPos, nil
   225  }
   226  
   227  func copyEscapable(dst io.Writer, src io.ReadCloser, keys []byte) (written int64, err error) {
   228  	if len(keys) == 0 {
   229  		keys = defaultEscapeSequence
   230  	}
   231  	pr := &ttyProxy{escapeKeys: keys, r: src}
   232  	defer src.Close()
   233  
   234  	return io.Copy(dst, pr)
   235  }