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 }