github.com/demonoid81/moby@v0.0.0-20200517203328-62dd8e17c460/container/stream/attach.go (about) 1 package stream // import "github.com/demonoid81/moby/container/stream" 2 3 import ( 4 "context" 5 "io" 6 7 "github.com/demonoid81/moby/pkg/pools" 8 "github.com/moby/term" 9 "github.com/pkg/errors" 10 "github.com/sirupsen/logrus" 11 "golang.org/x/sync/errgroup" 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 group errgroup.Group 62 63 // Connect stdin of container to the attach stdin stream. 64 if cfg.Stdin != nil { 65 group.Go(func() error { 66 logrus.Debug("attach: stdin: begin") 67 defer logrus.Debug("attach: stdin: end") 68 69 defer func() { 70 if cfg.CloseStdin && !cfg.TTY { 71 cfg.CStdin.Close() 72 } else { 73 // No matter what, when stdin is closed (io.Copy unblock), close stdout and stderr 74 if cfg.CStdout != nil { 75 cfg.CStdout.Close() 76 } 77 if cfg.CStderr != nil { 78 cfg.CStderr.Close() 79 } 80 } 81 }() 82 83 var err error 84 if cfg.TTY { 85 _, err = copyEscapable(cfg.CStdin, cfg.Stdin, cfg.DetachKeys) 86 } else { 87 _, err = pools.Copy(cfg.CStdin, cfg.Stdin) 88 } 89 if err == io.ErrClosedPipe { 90 err = nil 91 } 92 if err != nil { 93 logrus.WithError(err).Debug("error on attach stdin") 94 return errors.Wrap(err, "error on attach stdin") 95 } 96 return nil 97 }) 98 } 99 100 attachStream := func(name string, stream io.Writer, streamPipe io.ReadCloser) error { 101 logrus.Debugf("attach: %s: begin", name) 102 defer logrus.Debugf("attach: %s: end", name) 103 defer func() { 104 // Make sure stdin gets closed 105 if cfg.Stdin != nil { 106 cfg.Stdin.Close() 107 } 108 streamPipe.Close() 109 }() 110 111 _, err := pools.Copy(stream, streamPipe) 112 if err == io.ErrClosedPipe { 113 err = nil 114 } 115 if err != nil { 116 logrus.WithError(err).Debugf("attach: %s", name) 117 return errors.Wrapf(err, "error attaching %s stream", name) 118 } 119 return nil 120 } 121 122 if cfg.Stdout != nil { 123 group.Go(func() error { 124 return attachStream("stdout", cfg.Stdout, cfg.CStdout) 125 }) 126 } 127 if cfg.Stderr != nil { 128 group.Go(func() error { 129 return attachStream("stderr", cfg.Stderr, cfg.CStderr) 130 }) 131 } 132 133 errs := make(chan error, 1) 134 go func() { 135 defer logrus.Debug("attach done") 136 groupErr := make(chan error, 1) 137 go func() { 138 groupErr <- group.Wait() 139 }() 140 select { 141 case <-ctx.Done(): 142 // close all pipes 143 if cfg.CStdin != nil { 144 cfg.CStdin.Close() 145 } 146 if cfg.CStdout != nil { 147 cfg.CStdout.Close() 148 } 149 if cfg.CStderr != nil { 150 cfg.CStderr.Close() 151 } 152 153 // Now with these closed, wait should return. 154 if err := group.Wait(); err != nil { 155 errs <- err 156 return 157 } 158 errs <- ctx.Err() 159 case err := <-groupErr: 160 errs <- err 161 } 162 }() 163 164 return errs 165 } 166 167 func copyEscapable(dst io.Writer, src io.ReadCloser, keys []byte) (written int64, err error) { 168 if len(keys) == 0 { 169 keys = defaultEscapeSequence 170 } 171 pr := term.NewEscapeProxy(src, keys) 172 defer src.Close() 173 174 return pools.Copy(dst, pr) 175 }