github.com/pmorton/docker@v1.5.0/pkg/stdcopy/stdcopy.go (about)

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