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 }