Browse code

Refactor attaches `copyEscapable`

`copyEscapable` is a copy/paste of io.Copy with some added handling for
checking for the attach escape sequence.

This removes the copy/paste and uses `io.Copy` directly. To be able to
do this, it now implements an `io.Reader` which proxies to the main
reader but looks for the escape sequence.

Signed-off-by: Brian Goff <cpuguy83@gmail.com>

Brian Goff authored on 2017/01/22 07:19:33
Showing 1 changed files
... ...
@@ -10,6 +10,8 @@ import (
10 10
 	"github.com/docker/docker/pkg/promise"
11 11
 )
12 12
 
13
+var defaultEscapeSequence = []byte{16, 17} // ctrl-p, ctrl-q
14
+
13 15
 // DetachError is special error which returned in case of container detach.
14 16
 type DetachError struct{}
15 17
 
... ...
@@ -153,57 +155,63 @@ func (c *Config) Attach(ctx context.Context, cfg *AttachConfig) chan error {
153 153
 	})
154 154
 }
155 155
 
156
-// Code c/c from io.Copy() modified to handle escape sequence
157
-func copyEscapable(dst io.Writer, src io.ReadCloser, keys []byte) (written int64, err error) {
158
-	if len(keys) == 0 {
159
-		// Default keys : ctrl-p ctrl-q
160
-		keys = []byte{16, 17}
156
+// ttyProxy is used only for attaches with a TTY. It is used to proxy
157
+// stdin keypresses from the underlying reader and look for the passed in
158
+// escape key sequence to signal a detach.
159
+type ttyProxy struct {
160
+	escapeKeys   []byte
161
+	escapeKeyPos int
162
+	r            io.Reader
163
+}
164
+
165
+func (r *ttyProxy) Read(buf []byte) (int, error) {
166
+	nr, err := r.r.Read(buf)
167
+
168
+	preserve := func() {
169
+		// this preserves the original key presses in the passed in buffer
170
+		nr += r.escapeKeyPos
171
+		preserve := make([]byte, 0, r.escapeKeyPos+len(buf))
172
+		preserve = append(preserve, r.escapeKeys[:r.escapeKeyPos]...)
173
+		preserve = append(preserve, buf...)
174
+		r.escapeKeyPos = 0
175
+		copy(buf[0:nr], preserve)
161 176
 	}
162
-	buf := make([]byte, 32*1024)
163
-	for {
164
-		nr, er := src.Read(buf)
165
-		if nr > 0 {
166
-			// ---- Docker addition
167
-			preservBuf := []byte{}
168
-			for i, key := range keys {
169
-				preservBuf = append(preservBuf, buf[0:nr]...)
170
-				if nr != 1 || buf[0] != key {
171
-					break
172
-				}
173
-				if i == len(keys)-1 {
174
-					src.Close()
175
-					return 0, DetachError{}
176
-				}
177
-				nr, er = src.Read(buf)
178
-			}
179
-			var nw int
180
-			var ew error
181
-			if len(preservBuf) > 0 {
182
-				nw, ew = dst.Write(preservBuf)
183
-				nr = len(preservBuf)
184
-			} else {
185
-				// ---- End of docker
186
-				nw, ew = dst.Write(buf[0:nr])
187
-			}
188
-			if nw > 0 {
189
-				written += int64(nw)
190
-			}
191
-			if ew != nil {
192
-				err = ew
193
-				break
194
-			}
195
-			if nr != nw {
196
-				err = io.ErrShortWrite
197
-				break
198
-			}
199
-		}
200
-		if er == io.EOF {
201
-			break
177
+
178
+	if nr != 1 || err != nil {
179
+		if r.escapeKeyPos > 0 {
180
+			preserve()
202 181
 		}
203
-		if er != nil {
204
-			err = er
205
-			break
182
+		return nr, err
183
+	}
184
+
185
+	if buf[0] != r.escapeKeys[r.escapeKeyPos] {
186
+		if r.escapeKeyPos > 0 {
187
+			preserve()
206 188
 		}
189
+		return nr, nil
190
+	}
191
+
192
+	if r.escapeKeyPos == len(r.escapeKeys)-1 {
193
+		return 0, DetachError{}
207 194
 	}
208
-	return written, err
195
+
196
+	// Looks like we've got an escape key, but we need to match again on the next
197
+	// read.
198
+	// Store the current escape key we found so we can look for the next one on
199
+	// the next read.
200
+	// Since this is an escape key, make sure we don't let the caller read it
201
+	// If later on we find that this is not the escape sequence, we'll add the
202
+	// keys back
203
+	r.escapeKeyPos++
204
+	return nr - r.escapeKeyPos, nil
205
+}
206
+
207
+func copyEscapable(dst io.Writer, src io.ReadCloser, keys []byte) (written int64, err error) {
208
+	if len(keys) == 0 {
209
+		keys = defaultEscapeSequence
210
+	}
211
+	pr := &ttyProxy{escapeKeys: keys, r: src}
212
+	defer src.Close()
213
+
214
+	return io.Copy(dst, pr)
209 215
 }