github.com/demonoid81/containerd@v1.3.4/pkg/process/io.go (about) 1 // +build !windows 2 3 /* 4 Copyright The containerd Authors. 5 6 Licensed under the Apache License, Version 2.0 (the "License"); 7 you may not use this file except in compliance with the License. 8 You may obtain a copy of the License at 9 10 http://www.apache.org/licenses/LICENSE-2.0 11 12 Unless required by applicable law or agreed to in writing, software 13 distributed under the License is distributed on an "AS IS" BASIS, 14 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 See the License for the specific language governing permissions and 16 limitations under the License. 17 */ 18 19 package process 20 21 import ( 22 "context" 23 "fmt" 24 "io" 25 "net/url" 26 "os" 27 "os/exec" 28 "path/filepath" 29 "sync" 30 "sync/atomic" 31 "syscall" 32 "time" 33 34 "github.com/containerd/containerd/log" 35 "github.com/containerd/containerd/namespaces" 36 "github.com/containerd/containerd/pkg/stdio" 37 "github.com/containerd/containerd/pkg/timeout" 38 "github.com/containerd/containerd/sys" 39 "github.com/containerd/fifo" 40 runc "github.com/containerd/go-runc" 41 "github.com/hashicorp/go-multierror" 42 "github.com/pkg/errors" 43 ) 44 45 const ( 46 shimLoggerTermTimeout = "io.containerd.timeout.shim.logger.shutdown" 47 ) 48 49 var bufPool = sync.Pool{ 50 New: func() interface{} { 51 // setting to 4096 to align with PIPE_BUF 52 // http://man7.org/linux/man-pages/man7/pipe.7.html 53 buffer := make([]byte, 4096) 54 return &buffer 55 }, 56 } 57 58 type processIO struct { 59 io runc.IO 60 61 uri *url.URL 62 copy bool 63 stdio stdio.Stdio 64 } 65 66 func (p *processIO) Close() error { 67 if p.io != nil { 68 return p.io.Close() 69 } 70 return nil 71 } 72 73 func (p *processIO) IO() runc.IO { 74 return p.io 75 } 76 77 func (p *processIO) Copy(ctx context.Context, wg *sync.WaitGroup) error { 78 if !p.copy { 79 return nil 80 } 81 var cwg sync.WaitGroup 82 if err := copyPipes(ctx, p.IO(), p.stdio.Stdin, p.stdio.Stdout, p.stdio.Stderr, wg, &cwg); err != nil { 83 return errors.Wrap(err, "unable to copy pipes") 84 } 85 cwg.Wait() 86 return nil 87 } 88 89 func createIO(ctx context.Context, id string, ioUID, ioGID int, stdio stdio.Stdio) (*processIO, error) { 90 pio := &processIO{ 91 stdio: stdio, 92 } 93 if stdio.IsNull() { 94 i, err := runc.NewNullIO() 95 if err != nil { 96 return nil, err 97 } 98 pio.io = i 99 return pio, nil 100 } 101 u, err := url.Parse(stdio.Stdout) 102 if err != nil { 103 return nil, errors.Wrap(err, "unable to parse stdout uri") 104 } 105 if u.Scheme == "" { 106 u.Scheme = "fifo" 107 } 108 pio.uri = u 109 switch u.Scheme { 110 case "fifo": 111 pio.copy = true 112 pio.io, err = runc.NewPipeIO(ioUID, ioGID, withConditionalIO(stdio)) 113 case "binary": 114 pio.io, err = NewBinaryIO(ctx, id, u) 115 case "file": 116 filePath := u.Path 117 if err := os.MkdirAll(filepath.Dir(filePath), 0755); err != nil { 118 return nil, err 119 } 120 var f *os.File 121 f, err = os.OpenFile(filePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) 122 if err != nil { 123 return nil, err 124 } 125 f.Close() 126 pio.stdio.Stdout = filePath 127 pio.stdio.Stderr = filePath 128 pio.copy = true 129 pio.io, err = runc.NewPipeIO(ioUID, ioGID, withConditionalIO(stdio)) 130 default: 131 return nil, errors.Errorf("unknown STDIO scheme %s", u.Scheme) 132 } 133 if err != nil { 134 return nil, err 135 } 136 return pio, nil 137 } 138 139 func copyPipes(ctx context.Context, rio runc.IO, stdin, stdout, stderr string, wg, cwg *sync.WaitGroup) error { 140 var sameFile *countingWriteCloser 141 for _, i := range []struct { 142 name string 143 dest func(wc io.WriteCloser, rc io.Closer) 144 }{ 145 { 146 name: stdout, 147 dest: func(wc io.WriteCloser, rc io.Closer) { 148 wg.Add(1) 149 cwg.Add(1) 150 go func() { 151 cwg.Done() 152 p := bufPool.Get().(*[]byte) 153 defer bufPool.Put(p) 154 if _, err := io.CopyBuffer(wc, rio.Stdout(), *p); err != nil { 155 log.G(ctx).Warn("error copying stdout") 156 } 157 wg.Done() 158 wc.Close() 159 if rc != nil { 160 rc.Close() 161 } 162 }() 163 }, 164 }, { 165 name: stderr, 166 dest: func(wc io.WriteCloser, rc io.Closer) { 167 wg.Add(1) 168 cwg.Add(1) 169 go func() { 170 cwg.Done() 171 p := bufPool.Get().(*[]byte) 172 defer bufPool.Put(p) 173 if _, err := io.CopyBuffer(wc, rio.Stderr(), *p); err != nil { 174 log.G(ctx).Warn("error copying stderr") 175 } 176 wg.Done() 177 wc.Close() 178 if rc != nil { 179 rc.Close() 180 } 181 }() 182 }, 183 }, 184 } { 185 ok, err := sys.IsFifo(i.name) 186 if err != nil { 187 return err 188 } 189 var ( 190 fw io.WriteCloser 191 fr io.Closer 192 ) 193 if ok { 194 if fw, err = fifo.OpenFifo(ctx, i.name, syscall.O_WRONLY, 0); err != nil { 195 return errors.Wrapf(err, "containerd-shim: opening w/o fifo %q failed", i.name) 196 } 197 if fr, err = fifo.OpenFifo(ctx, i.name, syscall.O_RDONLY, 0); err != nil { 198 return errors.Wrapf(err, "containerd-shim: opening r/o fifo %q failed", i.name) 199 } 200 } else { 201 if sameFile != nil { 202 sameFile.count++ 203 i.dest(sameFile, nil) 204 continue 205 } 206 if fw, err = os.OpenFile(i.name, syscall.O_WRONLY|syscall.O_APPEND, 0); err != nil { 207 return errors.Wrapf(err, "containerd-shim: opening file %q failed", i.name) 208 } 209 if stdout == stderr { 210 sameFile = &countingWriteCloser{ 211 WriteCloser: fw, 212 count: 1, 213 } 214 } 215 } 216 i.dest(fw, fr) 217 } 218 if stdin == "" { 219 return nil 220 } 221 f, err := fifo.OpenFifo(context.Background(), stdin, syscall.O_RDONLY|syscall.O_NONBLOCK, 0) 222 if err != nil { 223 return fmt.Errorf("containerd-shim: opening %s failed: %s", stdin, err) 224 } 225 cwg.Add(1) 226 go func() { 227 cwg.Done() 228 p := bufPool.Get().(*[]byte) 229 defer bufPool.Put(p) 230 231 io.CopyBuffer(rio.Stdin(), f, *p) 232 rio.Stdin().Close() 233 f.Close() 234 }() 235 return nil 236 } 237 238 // countingWriteCloser masks io.Closer() until close has been invoked a certain number of times. 239 type countingWriteCloser struct { 240 io.WriteCloser 241 count int64 242 } 243 244 func (c *countingWriteCloser) Close() error { 245 if atomic.AddInt64(&c.count, -1) > 0 { 246 return nil 247 } 248 return c.WriteCloser.Close() 249 } 250 251 // NewBinaryIO runs a custom binary process for pluggable shim logging 252 func NewBinaryIO(ctx context.Context, id string, uri *url.URL) (runc.IO, error) { 253 ns, err := namespaces.NamespaceRequired(ctx) 254 if err != nil { 255 return nil, err 256 } 257 var args []string 258 for k, vs := range uri.Query() { 259 args = append(args, k) 260 if len(vs) > 0 { 261 args = append(args, vs[0]) 262 } 263 } 264 265 out, err := newPipe() 266 if err != nil { 267 return nil, err 268 } 269 270 serr, err := newPipe() 271 if err != nil { 272 return nil, err 273 } 274 275 r, w, err := os.Pipe() 276 if err != nil { 277 return nil, err 278 } 279 280 cmd := exec.Command(uri.Path, args...) 281 cmd.Env = append(cmd.Env, 282 "CONTAINER_ID="+id, 283 "CONTAINER_NAMESPACE="+ns, 284 ) 285 286 cmd.ExtraFiles = append(cmd.ExtraFiles, out.r, serr.r, w) 287 // don't need to register this with the reaper or wait when 288 // running inside a shim 289 if err := cmd.Start(); err != nil { 290 return nil, err 291 } 292 // close our side of the pipe after start 293 if err := w.Close(); err != nil { 294 return nil, err 295 } 296 // wait for the logging binary to be ready 297 b := make([]byte, 1) 298 if _, err := r.Read(b); err != nil && err != io.EOF { 299 return nil, err 300 } 301 return &binaryIO{ 302 cmd: cmd, 303 out: out, 304 err: serr, 305 }, nil 306 } 307 308 type binaryIO struct { 309 cmd *exec.Cmd 310 out, err *pipe 311 } 312 313 func (b *binaryIO) CloseAfterStart() error { 314 var ( 315 result *multierror.Error 316 ) 317 318 for _, v := range []*pipe{b.out, b.err} { 319 if v != nil { 320 if err := v.r.Close(); err != nil { 321 result = multierror.Append(result, err) 322 } 323 } 324 } 325 326 return result.ErrorOrNil() 327 } 328 329 func (b *binaryIO) Close() error { 330 var ( 331 result *multierror.Error 332 ) 333 334 for _, v := range []*pipe{b.out, b.err} { 335 if v != nil { 336 if err := v.Close(); err != nil { 337 result = multierror.Append(result, err) 338 } 339 } 340 } 341 342 if err := b.cancel(); err != nil { 343 result = multierror.Append(result, err) 344 } 345 346 return result.ErrorOrNil() 347 } 348 349 func (b *binaryIO) cancel() error { 350 if b.cmd == nil || b.cmd.Process == nil { 351 return nil 352 } 353 354 // Send SIGTERM first, so logger process has a chance to flush and exit properly 355 if err := b.cmd.Process.Signal(syscall.SIGTERM); err != nil { 356 result := multierror.Append(errors.Wrap(err, "failed to send SIGTERM")) 357 358 log.L.WithError(err).Warn("failed to send SIGTERM signal, killing logging shim") 359 360 if err := b.cmd.Process.Kill(); err != nil { 361 result = multierror.Append(result, errors.Wrap(err, "failed to kill process after faulty SIGTERM")) 362 } 363 364 return result.ErrorOrNil() 365 } 366 367 done := make(chan error) 368 go func() { 369 err := b.cmd.Wait() 370 if err != nil { 371 err = errors.Wrap(err, "failed to wait for shim logger process after SIGTERM") 372 } 373 done <- err 374 }() 375 376 termTimeout := timeout.Get(shimLoggerTermTimeout) 377 378 select { 379 case err := <-done: 380 return err 381 case <-time.After(termTimeout): 382 log.L.Warn("failed to wait for shim logger process to exit, killing") 383 384 err := b.cmd.Process.Kill() 385 if err != nil { 386 return errors.Wrap(err, "failed to kill shim logger process") 387 } 388 389 return nil 390 } 391 } 392 393 func (b *binaryIO) Stdin() io.WriteCloser { 394 return nil 395 } 396 397 func (b *binaryIO) Stdout() io.ReadCloser { 398 return nil 399 } 400 401 func (b *binaryIO) Stderr() io.ReadCloser { 402 return nil 403 } 404 405 func (b *binaryIO) Set(cmd *exec.Cmd) { 406 if b.out != nil { 407 cmd.Stdout = b.out.w 408 } 409 if b.err != nil { 410 cmd.Stderr = b.err.w 411 } 412 } 413 414 func newPipe() (*pipe, error) { 415 r, w, err := os.Pipe() 416 if err != nil { 417 return nil, err 418 } 419 return &pipe{ 420 r: r, 421 w: w, 422 }, nil 423 } 424 425 type pipe struct { 426 r *os.File 427 w *os.File 428 } 429 430 func (p *pipe) Close() error { 431 err := p.w.Close() 432 if rerr := p.r.Close(); err == nil { 433 err = rerr 434 } 435 return err 436 }