github.com/demonoid81/containerd@v1.3.4/cio/io.go (about) 1 /* 2 Copyright The containerd Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package cio 18 19 import ( 20 "context" 21 "errors" 22 "fmt" 23 "io" 24 "net/url" 25 "os" 26 "path/filepath" 27 "strings" 28 "sync" 29 30 "github.com/containerd/containerd/defaults" 31 ) 32 33 var bufPool = sync.Pool{ 34 New: func() interface{} { 35 buffer := make([]byte, 32<<10) 36 return &buffer 37 }, 38 } 39 40 // Config holds the IO configurations. 41 type Config struct { 42 // Terminal is true if one has been allocated 43 Terminal bool 44 // Stdin path 45 Stdin string 46 // Stdout path 47 Stdout string 48 // Stderr path 49 Stderr string 50 } 51 52 // IO holds the io information for a task or process 53 type IO interface { 54 // Config returns the IO configuration. 55 Config() Config 56 // Cancel aborts all current io operations. 57 Cancel() 58 // Wait blocks until all io copy operations have completed. 59 Wait() 60 // Close cleans up all open io resources. Cancel() is always called before 61 // Close() 62 Close() error 63 } 64 65 // Creator creates new IO sets for a task 66 type Creator func(id string) (IO, error) 67 68 // Attach allows callers to reattach to running tasks 69 // 70 // There should only be one reader for a task's IO set 71 // because fifo's can only be read from one reader or the output 72 // will be sent only to the first reads 73 type Attach func(*FIFOSet) (IO, error) 74 75 // FIFOSet is a set of file paths to FIFOs for a task's standard IO streams 76 type FIFOSet struct { 77 Config 78 close func() error 79 } 80 81 // Close the FIFOSet 82 func (f *FIFOSet) Close() error { 83 if f.close != nil { 84 return f.close() 85 } 86 return nil 87 } 88 89 // NewFIFOSet returns a new FIFOSet from a Config and a close function 90 func NewFIFOSet(config Config, close func() error) *FIFOSet { 91 return &FIFOSet{Config: config, close: close} 92 } 93 94 // Streams used to configure a Creator or Attach 95 type Streams struct { 96 Stdin io.Reader 97 Stdout io.Writer 98 Stderr io.Writer 99 Terminal bool 100 FIFODir string 101 } 102 103 // Opt customize options for creating a Creator or Attach 104 type Opt func(*Streams) 105 106 // WithStdio sets stream options to the standard input/output streams 107 func WithStdio(opt *Streams) { 108 WithStreams(os.Stdin, os.Stdout, os.Stderr)(opt) 109 } 110 111 // WithTerminal sets the terminal option 112 func WithTerminal(opt *Streams) { 113 opt.Terminal = true 114 } 115 116 // WithStreams sets the stream options to the specified Reader and Writers 117 func WithStreams(stdin io.Reader, stdout, stderr io.Writer) Opt { 118 return func(opt *Streams) { 119 opt.Stdin = stdin 120 opt.Stdout = stdout 121 opt.Stderr = stderr 122 } 123 } 124 125 // WithFIFODir sets the fifo directory. 126 // e.g. "/run/containerd/fifo", "/run/users/1001/containerd/fifo" 127 func WithFIFODir(dir string) Opt { 128 return func(opt *Streams) { 129 opt.FIFODir = dir 130 } 131 } 132 133 // NewCreator returns an IO creator from the options 134 func NewCreator(opts ...Opt) Creator { 135 streams := &Streams{} 136 for _, opt := range opts { 137 opt(streams) 138 } 139 if streams.FIFODir == "" { 140 streams.FIFODir = defaults.DefaultFIFODir 141 } 142 return func(id string) (IO, error) { 143 fifos, err := NewFIFOSetInDir(streams.FIFODir, id, streams.Terminal) 144 if err != nil { 145 return nil, err 146 } 147 if streams.Stdin == nil { 148 fifos.Stdin = "" 149 } 150 if streams.Stdout == nil { 151 fifos.Stdout = "" 152 } 153 if streams.Stderr == nil { 154 fifos.Stderr = "" 155 } 156 return copyIO(fifos, streams) 157 } 158 } 159 160 // NewAttach attaches the existing io for a task to the provided io.Reader/Writers 161 func NewAttach(opts ...Opt) Attach { 162 streams := &Streams{} 163 for _, opt := range opts { 164 opt(streams) 165 } 166 return func(fifos *FIFOSet) (IO, error) { 167 if fifos == nil { 168 return nil, fmt.Errorf("cannot attach, missing fifos") 169 } 170 return copyIO(fifos, streams) 171 } 172 } 173 174 // NullIO redirects the container's IO into /dev/null 175 func NullIO(_ string) (IO, error) { 176 return &cio{}, nil 177 } 178 179 // cio is a basic container IO implementation. 180 type cio struct { 181 config Config 182 wg *sync.WaitGroup 183 closers []io.Closer 184 cancel context.CancelFunc 185 } 186 187 func (c *cio) Config() Config { 188 return c.config 189 } 190 191 func (c *cio) Wait() { 192 if c.wg != nil { 193 c.wg.Wait() 194 } 195 } 196 197 func (c *cio) Close() error { 198 var lastErr error 199 for _, closer := range c.closers { 200 if closer == nil { 201 continue 202 } 203 if err := closer.Close(); err != nil { 204 lastErr = err 205 } 206 } 207 return lastErr 208 } 209 210 func (c *cio) Cancel() { 211 if c.cancel != nil { 212 c.cancel() 213 } 214 } 215 216 type pipes struct { 217 Stdin io.WriteCloser 218 Stdout io.ReadCloser 219 Stderr io.ReadCloser 220 } 221 222 // DirectIO allows task IO to be handled externally by the caller 223 type DirectIO struct { 224 pipes 225 cio 226 } 227 228 var ( 229 _ IO = &DirectIO{} 230 _ IO = &logURI{} 231 ) 232 233 // LogURI provides the raw logging URI 234 func LogURI(uri *url.URL) Creator { 235 return func(_ string) (IO, error) { 236 return &logURI{ 237 config: Config{ 238 Stdout: uri.String(), 239 Stderr: uri.String(), 240 }, 241 }, nil 242 } 243 } 244 245 // BinaryIO forwards container STDOUT|STDERR directly to a logging binary 246 func BinaryIO(binary string, args map[string]string) Creator { 247 return func(_ string) (IO, error) { 248 binary = filepath.Clean(binary) 249 if !strings.HasPrefix(binary, "/") { 250 return nil, errors.New("absolute path needed") 251 } 252 uri := &url.URL{ 253 Scheme: "binary", 254 Path: binary, 255 } 256 q := uri.Query() 257 for k, v := range args { 258 q.Set(k, v) 259 } 260 uri.RawQuery = q.Encode() 261 res := uri.String() 262 return &logURI{ 263 config: Config{ 264 Stdout: res, 265 Stderr: res, 266 }, 267 }, nil 268 } 269 } 270 271 // LogFile creates a file on disk that logs the task's STDOUT,STDERR. 272 // If the log file already exists, the logs will be appended to the file. 273 func LogFile(path string) Creator { 274 return func(_ string) (IO, error) { 275 path = filepath.Clean(path) 276 if !strings.HasPrefix(path, "/") { 277 return nil, errors.New("absolute path needed") 278 } 279 uri := &url.URL{ 280 Scheme: "file", 281 Path: path, 282 } 283 res := uri.String() 284 return &logURI{ 285 config: Config{ 286 Stdout: res, 287 Stderr: res, 288 }, 289 }, nil 290 } 291 } 292 293 type logURI struct { 294 config Config 295 } 296 297 func (l *logURI) Config() Config { 298 return l.config 299 } 300 301 func (l *logURI) Cancel() { 302 303 } 304 305 func (l *logURI) Wait() { 306 307 } 308 309 func (l *logURI) Close() error { 310 return nil 311 } 312 313 // Load the io for a container but do not attach 314 // 315 // Allows io to be loaded on the task for deletion without 316 // starting copy routines 317 func Load(set *FIFOSet) (IO, error) { 318 return &cio{ 319 config: set.Config, 320 closers: []io.Closer{set}, 321 }, nil 322 } 323 324 func (p *pipes) closers() []io.Closer { 325 return []io.Closer{p.Stdin, p.Stdout, p.Stderr} 326 }