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 }