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