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