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  }