mirror of
https://github.com/moby/moby.git
synced 2026-01-11 18:51:37 +00:00
Clients have no need for muxing streams using our StdCopy wire format. Signed-off-by: Cory Snider <csnider@mirantis.com>
147 lines
4.2 KiB
Go
147 lines
4.2 KiB
Go
package stdcopy
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
)
|
|
|
|
// StdType is the type of standard stream
|
|
// a writer can multiplex to.
|
|
type StdType byte
|
|
|
|
const (
|
|
Stdin StdType = 0 // Stdin represents standard input stream. It is present for completeness and should NOT be used. When reading the stream with [StdCopy] it is output on [Stdout].
|
|
Stdout StdType = 1 // Stdout represents standard output stream.
|
|
Stderr StdType = 2 // Stderr represents standard error steam.
|
|
Systemerr StdType = 3 // Systemerr represents errors originating from the system. When reading the stream with [StdCopy] it is returned as an error.
|
|
)
|
|
|
|
const (
|
|
stdWriterPrefixLen = 8
|
|
stdWriterFdIndex = 0
|
|
stdWriterSizeIndex = 4
|
|
|
|
startingBufLen = 32*1024 + stdWriterPrefixLen + 1
|
|
)
|
|
|
|
// StdCopy is a modified version of [io.Copy] to de-multiplex messages
|
|
// from "multiplexedSource" and copy them to destination streams
|
|
// "destOut" and "destErr".
|
|
//
|
|
// StdCopy demultiplexes "multiplexedSource", assuming that it contains
|
|
// two streams, previously multiplexed using a writer created with
|
|
// [NewStdWriter].
|
|
//
|
|
// As it reads from "multiplexedSource", StdCopy writes [Stdout] messages
|
|
// to "destOut", and [Stderr] message to "destErr]. For backward-compatibility,
|
|
// [Stdin] messages are output to "destOut". The [Systemerr] stream provides
|
|
// errors produced by the daemon. It is returned as an error, and terminates
|
|
// processing the stream.
|
|
//
|
|
// StdCopy it reads until it hits [io.EOF] on "multiplexedSource", after
|
|
// which it returns a nil error. In other words: any error returned indicates
|
|
// a real underlying error, which may be when an unknown [StdType] stream
|
|
// is received.
|
|
//
|
|
// The "written" return holds the total number of bytes written to "destOut"
|
|
// and "destErr" combined.
|
|
func StdCopy(destOut, destErr io.Writer, multiplexedSource io.Reader) (written int64, _ error) {
|
|
var (
|
|
buf = make([]byte, startingBufLen)
|
|
bufLen = len(buf)
|
|
nr, nw int
|
|
err error
|
|
out io.Writer
|
|
frameSize int
|
|
)
|
|
|
|
for {
|
|
// Make sure we have at least a full header
|
|
for nr < stdWriterPrefixLen {
|
|
var nr2 int
|
|
nr2, err = multiplexedSource.Read(buf[nr:])
|
|
nr += nr2
|
|
if errors.Is(err, io.EOF) {
|
|
if nr < stdWriterPrefixLen {
|
|
return written, nil
|
|
}
|
|
break
|
|
}
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
}
|
|
|
|
// Check the first byte to know where to write
|
|
stream := StdType(buf[stdWriterFdIndex])
|
|
switch stream {
|
|
case Stdin:
|
|
fallthrough
|
|
case Stdout:
|
|
// Write on stdout
|
|
out = destOut
|
|
case Stderr:
|
|
// Write on stderr
|
|
out = destErr
|
|
case Systemerr:
|
|
// If we're on Systemerr, we won't write anywhere.
|
|
// NB: if this code changes later, make sure you don't try to write
|
|
// to outstream if Systemerr is the stream
|
|
out = nil
|
|
default:
|
|
return 0, fmt.Errorf("unrecognized stream: %d", stream)
|
|
}
|
|
|
|
// Retrieve the size of the frame
|
|
frameSize = int(binary.BigEndian.Uint32(buf[stdWriterSizeIndex : stdWriterSizeIndex+4]))
|
|
|
|
// Check if the buffer is big enough to read the frame.
|
|
// Extend it if necessary.
|
|
if frameSize+stdWriterPrefixLen > bufLen {
|
|
buf = append(buf, make([]byte, frameSize+stdWriterPrefixLen-bufLen+1)...)
|
|
bufLen = len(buf)
|
|
}
|
|
|
|
// While the amount of bytes read is less than the size of the frame + header, we keep reading
|
|
for nr < frameSize+stdWriterPrefixLen {
|
|
var nr2 int
|
|
nr2, err = multiplexedSource.Read(buf[nr:])
|
|
nr += nr2
|
|
if errors.Is(err, io.EOF) {
|
|
if nr < frameSize+stdWriterPrefixLen {
|
|
return written, nil
|
|
}
|
|
break
|
|
}
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
}
|
|
|
|
// we might have an error from the source mixed up in our multiplexed
|
|
// stream. if we do, return it.
|
|
if stream == Systemerr {
|
|
return written, fmt.Errorf("error from daemon in stream: %s", string(buf[stdWriterPrefixLen:frameSize+stdWriterPrefixLen]))
|
|
}
|
|
|
|
// Write the retrieved frame (without header)
|
|
nw, err = out.Write(buf[stdWriterPrefixLen : frameSize+stdWriterPrefixLen])
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
// If the frame has not been fully written: error
|
|
if nw != frameSize {
|
|
return 0, io.ErrShortWrite
|
|
}
|
|
written += int64(nw)
|
|
|
|
// Move the rest of the buffer to the beginning
|
|
copy(buf, buf[frameSize+stdWriterPrefixLen:])
|
|
// Move the index
|
|
nr -= frameSize + stdWriterPrefixLen
|
|
}
|
|
}
|