github.com/michael-k/docker@v1.7.0-rc2/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 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 stdcopy.Stdin, stdcopy.Stdout, stdcopy.Stderr. 56 func NewStdWriter(w io.Writer, t StdType) *StdWriter { 57 return &StdWriter{ 58 Writer: w, 59 prefix: t, 60 sizeBuf: make([]byte, 4), 61 } 62 } 63 64 var ErrInvalidStdHeader = errors.New("Unrecognized input header") 65 66 // StdCopy is a modified version of io.Copy. 67 // 68 // StdCopy will demultiplex `src`, assuming that it contains two streams, 69 // previously multiplexed together using a StdWriter instance. 70 // As it reads from `src`, StdCopy will write to `dstout` and `dsterr`. 71 // 72 // StdCopy will read until it hits EOF on `src`. It will then return a nil error. 73 // In other words: if `err` is non nil, it indicates a real underlying error. 74 // 75 // `written` will hold the total number of bytes written to `dstout` and `dsterr`. 76 func StdCopy(dstout, dsterr io.Writer, src io.Reader) (written int64, err error) { 77 var ( 78 buf = make([]byte, 32*1024+StdWriterPrefixLen+1) 79 bufLen = len(buf) 80 nr, nw int 81 er, ew error 82 out io.Writer 83 frameSize int 84 ) 85 86 for { 87 // Make sure we have at least a full header 88 for nr < StdWriterPrefixLen { 89 var nr2 int 90 nr2, er = src.Read(buf[nr:]) 91 nr += nr2 92 if er == io.EOF { 93 if nr < StdWriterPrefixLen { 94 logrus.Debugf("Corrupted prefix: %v", buf[:nr]) 95 return written, nil 96 } 97 break 98 } 99 if er != nil { 100 logrus.Debugf("Error reading header: %s", er) 101 return 0, er 102 } 103 } 104 105 // Check the first byte to know where to write 106 switch buf[StdWriterFdIndex] { 107 case 0: 108 fallthrough 109 case 1: 110 // Write on stdout 111 out = dstout 112 case 2: 113 // Write on stderr 114 out = dsterr 115 default: 116 logrus.Debugf("Error selecting output fd: (%d)", buf[StdWriterFdIndex]) 117 return 0, ErrInvalidStdHeader 118 } 119 120 // Retrieve the size of the frame 121 frameSize = int(binary.BigEndian.Uint32(buf[StdWriterSizeIndex : StdWriterSizeIndex+4])) 122 logrus.Debugf("framesize: %d", frameSize) 123 124 // Check if the buffer is big enough to read the frame. 125 // Extend it if necessary. 126 if frameSize+StdWriterPrefixLen > bufLen { 127 logrus.Debugf("Extending buffer cap by %d (was %d)", frameSize+StdWriterPrefixLen-bufLen+1, len(buf)) 128 buf = append(buf, make([]byte, frameSize+StdWriterPrefixLen-bufLen+1)...) 129 bufLen = len(buf) 130 } 131 132 // While the amount of bytes read is less than the size of the frame + header, we keep reading 133 for nr < frameSize+StdWriterPrefixLen { 134 var nr2 int 135 nr2, er = src.Read(buf[nr:]) 136 nr += nr2 137 if er == io.EOF { 138 if nr < frameSize+StdWriterPrefixLen { 139 logrus.Debugf("Corrupted frame: %v", buf[StdWriterPrefixLen:nr]) 140 return written, nil 141 } 142 break 143 } 144 if er != nil { 145 logrus.Debugf("Error reading frame: %s", er) 146 return 0, er 147 } 148 } 149 150 // Write the retrieved frame (without header) 151 nw, ew = out.Write(buf[StdWriterPrefixLen : frameSize+StdWriterPrefixLen]) 152 if ew != nil { 153 logrus.Debugf("Error writing frame: %s", ew) 154 return 0, ew 155 } 156 // If the frame has not been fully written: error 157 if nw != frameSize { 158 logrus.Debugf("Error Short Write: (%d on %d)", nw, frameSize) 159 return 0, io.ErrShortWrite 160 } 161 written += int64(nw) 162 163 // Move the rest of the buffer to the beginning 164 copy(buf, buf[frameSize+StdWriterPrefixLen:]) 165 // Move the index 166 nr -= frameSize + StdWriterPrefixLen 167 } 168 }