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