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  }