Skip to content

Commit 626906f

Browse files
cpuguy83runcom
authored andcommitted
BACKPORT: Resolve race conditions in attach API call
Upstream reference: moby#30446 Signed-off-by: Jim Minter <jminter@redhat.com> Move attach code to stream package This cleans up attach a little bit, and moves it out of the container package. Really `AttachStream` is a method on `*stream.Config`, so moved if from a package level function to one bound to `Config`. In addition, uses a config struct rather than passing around tons and tons of arguments. Signed-off-by: Brian Goff <cpuguy83@gmail.com> 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> Signed-off-by: Antonio Murdaca <runcom@redhat.com>
1 parent 49319a4 commit 626906f

5 files changed

Lines changed: 325 additions & 237 deletions

File tree

container/container.go

Lines changed: 0 additions & 187 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ import (
3030
"github.com/docker/docker/libcontainerd"
3131
"github.com/docker/docker/pkg/idtools"
3232
"github.com/docker/docker/pkg/ioutils"
33-
"github.com/docker/docker/pkg/promise"
3433
"github.com/docker/docker/pkg/signal"
3534
"github.com/docker/docker/pkg/symlink"
3635
"github.com/docker/docker/restartmanager"
@@ -58,13 +57,6 @@ var (
5857
errInvalidNetwork = fmt.Errorf("invalid network settings while building port map info")
5958
)
6059

61-
// DetachError is special error which returned in case of container detach.
62-
type DetachError struct{}
63-
64-
func (DetachError) Error() string {
65-
return "detached from container"
66-
}
67-
6860
// CommonContainer holds the fields for a container which are
6961
// applicable across all platforms supported by the daemon.
7062
type CommonContainer struct {
@@ -366,185 +358,6 @@ func (container *Container) GetExecIDs() []string {
366358
return container.ExecCommands.List()
367359
}
368360

369-
// Attach connects to the container's TTY, delegating to standard
370-
// streams or websockets depending on the configuration.
371-
func (container *Container) Attach(stdin io.ReadCloser, stdout io.Writer, stderr io.Writer, keys []byte) chan error {
372-
ctx := container.InitAttachContext()
373-
return AttachStreams(ctx, container.StreamConfig, container.Config.OpenStdin, container.Config.StdinOnce, container.Config.Tty, stdin, stdout, stderr, keys)
374-
}
375-
376-
// AttachStreams connects streams to a TTY.
377-
// Used by exec too. Should this move somewhere else?
378-
func AttachStreams(ctx context.Context, streamConfig *stream.Config, openStdin, stdinOnce, tty bool, stdin io.ReadCloser, stdout io.Writer, stderr io.Writer, keys []byte) chan error {
379-
var (
380-
cStdout, cStderr io.ReadCloser
381-
cStdin io.WriteCloser
382-
wg sync.WaitGroup
383-
errors = make(chan error, 3)
384-
)
385-
386-
if stdin != nil && openStdin {
387-
cStdin = streamConfig.StdinPipe()
388-
wg.Add(1)
389-
}
390-
391-
if stdout != nil {
392-
cStdout = streamConfig.StdoutPipe()
393-
wg.Add(1)
394-
}
395-
396-
if stderr != nil {
397-
cStderr = streamConfig.StderrPipe()
398-
wg.Add(1)
399-
}
400-
401-
// Connect stdin of container to the http conn.
402-
go func() {
403-
if stdin == nil || !openStdin {
404-
return
405-
}
406-
logrus.Debug("attach: stdin: begin")
407-
408-
var err error
409-
if tty {
410-
_, err = copyEscapable(cStdin, stdin, keys)
411-
} else {
412-
_, err = io.Copy(cStdin, stdin)
413-
}
414-
if err == io.ErrClosedPipe {
415-
err = nil
416-
}
417-
if err != nil {
418-
logrus.Errorf("attach: stdin: %s", err)
419-
errors <- err
420-
}
421-
if stdinOnce && !tty {
422-
cStdin.Close()
423-
} else {
424-
// No matter what, when stdin is closed (io.Copy unblock), close stdout and stderr
425-
if cStdout != nil {
426-
cStdout.Close()
427-
}
428-
if cStderr != nil {
429-
cStderr.Close()
430-
}
431-
}
432-
logrus.Debug("attach: stdin: end")
433-
wg.Done()
434-
}()
435-
436-
attachStream := func(name string, stream io.Writer, streamPipe io.ReadCloser) {
437-
if stream == nil {
438-
return
439-
}
440-
441-
logrus.Debugf("attach: %s: begin", name)
442-
_, err := io.Copy(stream, streamPipe)
443-
if err == io.ErrClosedPipe {
444-
err = nil
445-
}
446-
if err != nil {
447-
logrus.Errorf("attach: %s: %v", name, err)
448-
errors <- err
449-
}
450-
// Make sure stdin gets closed
451-
if stdin != nil {
452-
stdin.Close()
453-
}
454-
streamPipe.Close()
455-
logrus.Debugf("attach: %s: end", name)
456-
wg.Done()
457-
}
458-
459-
go attachStream("stdout", stdout, cStdout)
460-
go attachStream("stderr", stderr, cStderr)
461-
462-
return promise.Go(func() error {
463-
done := make(chan struct{})
464-
go func() {
465-
wg.Wait()
466-
close(done)
467-
}()
468-
select {
469-
case <-done:
470-
case <-ctx.Done():
471-
// close all pipes
472-
if cStdin != nil {
473-
cStdin.Close()
474-
}
475-
if cStdout != nil {
476-
cStdout.Close()
477-
}
478-
if cStderr != nil {
479-
cStderr.Close()
480-
}
481-
<-done
482-
}
483-
close(errors)
484-
for err := range errors {
485-
if err != nil {
486-
return err
487-
}
488-
}
489-
return nil
490-
})
491-
}
492-
493-
// Code c/c from io.Copy() modified to handle escape sequence
494-
func copyEscapable(dst io.Writer, src io.ReadCloser, keys []byte) (written int64, err error) {
495-
if len(keys) == 0 {
496-
// Default keys : ctrl-p ctrl-q
497-
keys = []byte{16, 17}
498-
}
499-
buf := make([]byte, 32*1024)
500-
for {
501-
nr, er := src.Read(buf)
502-
if nr > 0 {
503-
// ---- Docker addition
504-
preservBuf := []byte{}
505-
for i, key := range keys {
506-
preservBuf = append(preservBuf, buf[0:nr]...)
507-
if nr != 1 || buf[0] != key {
508-
break
509-
}
510-
if i == len(keys)-1 {
511-
src.Close()
512-
return 0, DetachError{}
513-
}
514-
nr, er = src.Read(buf)
515-
}
516-
var nw int
517-
var ew error
518-
if len(preservBuf) > 0 {
519-
nw, ew = dst.Write(preservBuf)
520-
nr = len(preservBuf)
521-
} else {
522-
// ---- End of docker
523-
nw, ew = dst.Write(buf[0:nr])
524-
}
525-
if nw > 0 {
526-
written += int64(nw)
527-
}
528-
if ew != nil {
529-
err = ew
530-
break
531-
}
532-
if nr != nw {
533-
err = io.ErrShortWrite
534-
break
535-
}
536-
}
537-
if er == io.EOF {
538-
break
539-
}
540-
if er != nil {
541-
err = er
542-
break
543-
}
544-
}
545-
return written, err
546-
}
547-
548361
// ShouldRestart decides whether the daemon should restart the container or not.
549362
// This is based on the container's restart policy.
550363
func (container *Container) ShouldRestart() bool {

0 commit comments

Comments
 (0)