github.com/hustcat/docker@v1.3.3-0.20160314103604-901c67a8eeab/pkg/stdcopy/stdcopy.go (about)

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