github.com/olljanat/moby@v1.13.1/pkg/stdcopy/stdcopy.go (about)

     1  package stdcopy
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/binary"
     6  	"errors"
     7  	"fmt"
     8  	"io"
     9  	"sync"
    10  )
    11  
    12  // StdType is the type of standard stream
    13  // a writer can multiplex to.
    14  type StdType byte
    15  
    16  const (
    17  	// Stdin represents standard input stream type.
    18  	Stdin StdType = iota
    19  	// Stdout represents standard output stream type.
    20  	Stdout
    21  	// Stderr represents standard error steam type.
    22  	Stderr
    23  
    24  	stdWriterPrefixLen = 8
    25  	stdWriterFdIndex   = 0
    26  	stdWriterSizeIndex = 4
    27  
    28  	startingBufLen = 32*1024 + stdWriterPrefixLen + 1
    29  )
    30  
    31  var bufPool = &sync.Pool{New: func() interface{} { return bytes.NewBuffer(nil) }}
    32  
    33  // stdWriter is wrapper of io.Writer with extra customized info.
    34  type stdWriter struct {
    35  	io.Writer
    36  	prefix byte
    37  }
    38  
    39  // Write sends the buffer to the underneath writer.
    40  // It inserts the prefix header before the buffer,
    41  // so stdcopy.StdCopy knows where to multiplex the output.
    42  // It makes stdWriter to implement io.Writer.
    43  func (w *stdWriter) Write(p []byte) (n int, err error) {
    44  	if w == nil || w.Writer == nil {
    45  		return 0, errors.New("Writer not instantiated")
    46  	}
    47  	if p == nil {
    48  		return 0, nil
    49  	}
    50  
    51  	header := [stdWriterPrefixLen]byte{stdWriterFdIndex: w.prefix}
    52  	binary.BigEndian.PutUint32(header[stdWriterSizeIndex:], uint32(len(p)))
    53  	buf := bufPool.Get().(*bytes.Buffer)
    54  	buf.Write(header[:])
    55  	buf.Write(p)
    56  
    57  	n, err = w.Writer.Write(buf.Bytes())
    58  	n -= stdWriterPrefixLen
    59  	if n < 0 {
    60  		n = 0
    61  	}
    62  
    63  	buf.Reset()
    64  	bufPool.Put(buf)
    65  	return
    66  }
    67  
    68  // NewStdWriter instantiates a new Writer.
    69  // Everything written to it will be encapsulated using a custom format,
    70  // and written to the underlying `w` stream.
    71  // This allows multiple write streams (e.g. stdout and stderr) to be muxed into a single connection.
    72  // `t` indicates the id of the stream to encapsulate.
    73  // It can be stdcopy.Stdin, stdcopy.Stdout, stdcopy.Stderr.
    74  func NewStdWriter(w io.Writer, t StdType) io.Writer {
    75  	return &stdWriter{
    76  		Writer: w,
    77  		prefix: byte(t),
    78  	}
    79  }
    80  
    81  // StdCopy is a modified version of io.Copy.
    82  //
    83  // StdCopy will demultiplex `src`, assuming that it contains two streams,
    84  // previously multiplexed together using a StdWriter instance.
    85  // As it reads from `src`, StdCopy will write to `dstout` and `dsterr`.
    86  //
    87  // StdCopy will read until it hits EOF on `src`. It will then return a nil error.
    88  // In other words: if `err` is non nil, it indicates a real underlying error.
    89  //
    90  // `written` will hold the total number of bytes written to `dstout` and `dsterr`.
    91  func StdCopy(dstout, dsterr io.Writer, src io.Reader) (written int64, err error) {
    92  	var (
    93  		buf       = make([]byte, startingBufLen)
    94  		bufLen    = len(buf)
    95  		nr, nw    int
    96  		er, ew    error
    97  		out       io.Writer
    98  		frameSize int
    99  	)
   100  
   101  	for {
   102  		// Make sure we have at least a full header
   103  		for nr < stdWriterPrefixLen {
   104  			var nr2 int
   105  			nr2, er = src.Read(buf[nr:])
   106  			nr += nr2
   107  			if er == io.EOF {
   108  				if nr < stdWriterPrefixLen {
   109  					return written, nil
   110  				}
   111  				break
   112  			}
   113  			if er != nil {
   114  				return 0, er
   115  			}
   116  		}
   117  
   118  		// Check the first byte to know where to write
   119  		switch StdType(buf[stdWriterFdIndex]) {
   120  		case Stdin:
   121  			fallthrough
   122  		case Stdout:
   123  			// Write on stdout
   124  			out = dstout
   125  		case Stderr:
   126  			// Write on stderr
   127  			out = dsterr
   128  		default:
   129  			return 0, fmt.Errorf("Unrecognized input header: %d", buf[stdWriterFdIndex])
   130  		}
   131  
   132  		// Retrieve the size of the frame
   133  		frameSize = int(binary.BigEndian.Uint32(buf[stdWriterSizeIndex : stdWriterSizeIndex+4]))
   134  
   135  		// Check if the buffer is big enough to read the frame.
   136  		// Extend it if necessary.
   137  		if frameSize+stdWriterPrefixLen > bufLen {
   138  			buf = append(buf, make([]byte, frameSize+stdWriterPrefixLen-bufLen+1)...)
   139  			bufLen = len(buf)
   140  		}
   141  
   142  		// While the amount of bytes read is less than the size of the frame + header, we keep reading
   143  		for nr < frameSize+stdWriterPrefixLen {
   144  			var nr2 int
   145  			nr2, er = src.Read(buf[nr:])
   146  			nr += nr2
   147  			if er == io.EOF {
   148  				if nr < frameSize+stdWriterPrefixLen {
   149  					return written, nil
   150  				}
   151  				break
   152  			}
   153  			if er != nil {
   154  				return 0, er
   155  			}
   156  		}
   157  
   158  		// Write the retrieved frame (without header)
   159  		nw, ew = out.Write(buf[stdWriterPrefixLen : frameSize+stdWriterPrefixLen])
   160  		if ew != nil {
   161  			return 0, ew
   162  		}
   163  		// If the frame has not been fully written: error
   164  		if nw != frameSize {
   165  			return 0, io.ErrShortWrite
   166  		}
   167  		written += int64(nw)
   168  
   169  		// Move the rest of the buffer to the beginning
   170  		copy(buf, buf[frameSize+stdWriterPrefixLen:])
   171  		// Move the index
   172  		nr -= frameSize + stdWriterPrefixLen
   173  	}
   174  }