github.com/containerd/containerd@v22.0.0-20200918172823-438c87b8e050+incompatible/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 uri, err := LogURIGenerator("binary", binary, args) 249 if err != nil { 250 return nil, err 251 } 252 253 res := uri.String() 254 return &logURI{ 255 config: Config{ 256 Stdout: res, 257 Stderr: res, 258 }, 259 }, nil 260 } 261 } 262 263 // LogFile creates a file on disk that logs the task's STDOUT,STDERR. 264 // If the log file already exists, the logs will be appended to the file. 265 func LogFile(path string) Creator { 266 return func(_ string) (IO, error) { 267 uri, err := LogURIGenerator("file", path, nil) 268 if err != nil { 269 return nil, err 270 } 271 272 res := uri.String() 273 return &logURI{ 274 config: Config{ 275 Stdout: res, 276 Stderr: res, 277 }, 278 }, nil 279 } 280 } 281 282 // LogURIGenerator is the helper to generate log uri with specific scheme. 283 func LogURIGenerator(scheme string, path string, args map[string]string) (*url.URL, error) { 284 path = filepath.Clean(path) 285 if !strings.HasPrefix(path, "/") { 286 return nil, errors.New("absolute path needed") 287 } 288 289 uri := &url.URL{ 290 Scheme: scheme, 291 Path: path, 292 } 293 294 if len(args) == 0 { 295 return uri, nil 296 } 297 298 q := uri.Query() 299 for k, v := range args { 300 q.Set(k, v) 301 } 302 uri.RawQuery = q.Encode() 303 return uri, nil 304 } 305 306 type logURI struct { 307 config Config 308 } 309 310 func (l *logURI) Config() Config { 311 return l.config 312 } 313 314 func (l *logURI) Cancel() { 315 316 } 317 318 func (l *logURI) Wait() { 319 320 } 321 322 func (l *logURI) Close() error { 323 return nil 324 } 325 326 // Load the io for a container but do not attach 327 // 328 // Allows io to be loaded on the task for deletion without 329 // starting copy routines 330 func Load(set *FIFOSet) (IO, error) { 331 return &cio{ 332 config: set.Config, 333 closers: []io.Closer{set}, 334 }, nil 335 } 336 337 func (p *pipes) closers() []io.Closer { 338 return []io.Closer{p.Stdin, p.Stdout, p.Stderr} 339 }