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