github.com/Prakhar-Agarwal-byte/moby@v0.0.0-20231027092010-a14e3e8ab87e/container/stream/streams.go (about)

     1  package stream // import "github.com/Prakhar-Agarwal-byte/moby/container/stream"
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io"
     7  	"strings"
     8  	"sync"
     9  
    10  	"github.com/containerd/containerd/cio"
    11  	"github.com/containerd/log"
    12  	"github.com/Prakhar-Agarwal-byte/moby/pkg/broadcaster"
    13  	"github.com/Prakhar-Agarwal-byte/moby/pkg/ioutils"
    14  	"github.com/Prakhar-Agarwal-byte/moby/pkg/pools"
    15  )
    16  
    17  // Config holds information about I/O streams managed together.
    18  //
    19  // config.StdinPipe returns a WriteCloser which can be used to feed data
    20  // to the standard input of the streamConfig's active process.
    21  // config.StdoutPipe and streamConfig.StderrPipe each return a ReadCloser
    22  // which can be used to retrieve the standard output (and error) generated
    23  // by the container's active process. The output (and error) are actually
    24  // copied and delivered to all StdoutPipe and StderrPipe consumers, using
    25  // a kind of "broadcaster".
    26  type Config struct {
    27  	wg        sync.WaitGroup
    28  	stdout    *broadcaster.Unbuffered
    29  	stderr    *broadcaster.Unbuffered
    30  	stdin     io.ReadCloser
    31  	stdinPipe io.WriteCloser
    32  	dio       *cio.DirectIO
    33  }
    34  
    35  // NewConfig creates a stream config and initializes
    36  // the standard err and standard out to new unbuffered broadcasters.
    37  func NewConfig() *Config {
    38  	return &Config{
    39  		stderr: new(broadcaster.Unbuffered),
    40  		stdout: new(broadcaster.Unbuffered),
    41  	}
    42  }
    43  
    44  // Stdout returns the standard output in the configuration.
    45  func (c *Config) Stdout() *broadcaster.Unbuffered {
    46  	return c.stdout
    47  }
    48  
    49  // Stderr returns the standard error in the configuration.
    50  func (c *Config) Stderr() *broadcaster.Unbuffered {
    51  	return c.stderr
    52  }
    53  
    54  // Stdin returns the standard input in the configuration.
    55  func (c *Config) Stdin() io.ReadCloser {
    56  	return c.stdin
    57  }
    58  
    59  // StdinPipe returns an input writer pipe as an io.WriteCloser.
    60  func (c *Config) StdinPipe() io.WriteCloser {
    61  	return c.stdinPipe
    62  }
    63  
    64  // StdoutPipe creates a new io.ReadCloser with an empty bytes pipe.
    65  // It adds this new out pipe to the Stdout broadcaster.
    66  // This will block stdout if unconsumed.
    67  func (c *Config) StdoutPipe() io.ReadCloser {
    68  	bytesPipe := ioutils.NewBytesPipe()
    69  	c.stdout.Add(bytesPipe)
    70  	return bytesPipe
    71  }
    72  
    73  // StderrPipe creates a new io.ReadCloser with an empty bytes pipe.
    74  // It adds this new err pipe to the Stderr broadcaster.
    75  // This will block stderr if unconsumed.
    76  func (c *Config) StderrPipe() io.ReadCloser {
    77  	bytesPipe := ioutils.NewBytesPipe()
    78  	c.stderr.Add(bytesPipe)
    79  	return bytesPipe
    80  }
    81  
    82  // NewInputPipes creates new pipes for both standard inputs, Stdin and StdinPipe.
    83  func (c *Config) NewInputPipes() {
    84  	c.stdin, c.stdinPipe = io.Pipe()
    85  }
    86  
    87  // NewNopInputPipe creates a new input pipe that will silently drop all messages in the input.
    88  func (c *Config) NewNopInputPipe() {
    89  	c.stdinPipe = ioutils.NopWriteCloser(io.Discard)
    90  }
    91  
    92  // CloseStreams ensures that the configured streams are properly closed.
    93  func (c *Config) CloseStreams() error {
    94  	var errors []string
    95  
    96  	if c.stdin != nil {
    97  		if err := c.stdin.Close(); err != nil {
    98  			errors = append(errors, fmt.Sprintf("error close stdin: %s", err))
    99  		}
   100  	}
   101  
   102  	if err := c.stdout.Clean(); err != nil {
   103  		errors = append(errors, fmt.Sprintf("error close stdout: %s", err))
   104  	}
   105  
   106  	if err := c.stderr.Clean(); err != nil {
   107  		errors = append(errors, fmt.Sprintf("error close stderr: %s", err))
   108  	}
   109  
   110  	if len(errors) > 0 {
   111  		return fmt.Errorf(strings.Join(errors, "\n"))
   112  	}
   113  
   114  	return nil
   115  }
   116  
   117  // CopyToPipe connects streamconfig with a libcontainerd.IOPipe
   118  func (c *Config) CopyToPipe(iop *cio.DirectIO) {
   119  	ctx := context.TODO()
   120  
   121  	c.dio = iop
   122  	copyFunc := func(w io.Writer, r io.ReadCloser) {
   123  		c.wg.Add(1)
   124  		go func() {
   125  			if _, err := pools.Copy(w, r); err != nil {
   126  				log.G(ctx).Errorf("stream copy error: %v", err)
   127  			}
   128  			r.Close()
   129  			c.wg.Done()
   130  		}()
   131  	}
   132  
   133  	if iop.Stdout != nil {
   134  		copyFunc(c.Stdout(), iop.Stdout)
   135  	}
   136  	if iop.Stderr != nil {
   137  		copyFunc(c.Stderr(), iop.Stderr)
   138  	}
   139  
   140  	if stdin := c.Stdin(); stdin != nil {
   141  		if iop.Stdin != nil {
   142  			go func() {
   143  				pools.Copy(iop.Stdin, stdin)
   144  				if err := iop.Stdin.Close(); err != nil {
   145  					log.G(ctx).Warnf("failed to close stdin: %v", err)
   146  				}
   147  			}()
   148  		}
   149  	}
   150  }
   151  
   152  // Wait for the stream to close
   153  // Wait supports timeouts via the context to unblock and forcefully
   154  // close the io streams
   155  func (c *Config) Wait(ctx context.Context) {
   156  	done := make(chan struct{}, 1)
   157  	go func() {
   158  		c.wg.Wait()
   159  		close(done)
   160  	}()
   161  	select {
   162  	case <-done:
   163  	case <-ctx.Done():
   164  		if c.dio != nil {
   165  			c.dio.Cancel()
   166  			c.dio.Wait()
   167  			c.dio.Close()
   168  		}
   169  	}
   170  }