github.com/endocode/docker@v1.4.2-0.20160113120958-46eb4700391e/pkg/stdcopy/stdcopy.go (about)

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