dotcloud/docker

View on GitHub
pkg/stdcopy/stdcopy.go

Summary

Maintainability
B
5 hrs
Test Coverage
package stdcopy // import "github.com/docker/docker/pkg/stdcopy"

import (
    "bytes"
    "encoding/binary"
    "errors"
    "fmt"
    "io"
    "sync"
)

// StdType is the type of standard stream
// a writer can multiplex to.
type StdType byte

const (
    // Stdin represents standard input stream type.
    Stdin StdType = iota
    // Stdout represents standard output stream type.
    Stdout
    // Stderr represents standard error steam type.
    Stderr
    // Systemerr represents errors originating from the system that make it
    // into the multiplexed stream.
    Systemerr

    stdWriterPrefixLen = 8
    stdWriterFdIndex   = 0
    stdWriterSizeIndex = 4

    startingBufLen = 32*1024 + stdWriterPrefixLen + 1
)

var bufPool = &sync.Pool{New: func() interface{} { return bytes.NewBuffer(nil) }}

// stdWriter is wrapper of io.Writer with extra customized info.
type stdWriter struct {
    io.Writer
    prefix byte
}

// Write sends the buffer to the underneath writer.
// It inserts the prefix header before the buffer,
// so stdcopy.StdCopy knows where to multiplex the output.
// It makes stdWriter to implement io.Writer.
func (w *stdWriter) Write(p []byte) (n int, err error) {
    if w == nil || w.Writer == nil {
        return 0, errors.New("Writer not instantiated")
    }
    if p == nil {
        return 0, nil
    }

    header := [stdWriterPrefixLen]byte{stdWriterFdIndex: w.prefix}
    binary.BigEndian.PutUint32(header[stdWriterSizeIndex:], uint32(len(p)))
    buf := bufPool.Get().(*bytes.Buffer)
    buf.Write(header[:])
    buf.Write(p)

    n, err = w.Writer.Write(buf.Bytes())
    n -= stdWriterPrefixLen
    if n < 0 {
        n = 0
    }

    buf.Reset()
    bufPool.Put(buf)
    return
}

// NewStdWriter instantiates a new Writer.
// Everything written to it will be encapsulated using a custom format,
// and written to the underlying `w` stream.
// This allows multiple write streams (e.g. stdout and stderr) to be muxed into a single connection.
// `t` indicates the id of the stream to encapsulate.
// It can be stdcopy.Stdin, stdcopy.Stdout, stdcopy.Stderr.
func NewStdWriter(w io.Writer, t StdType) io.Writer {
    return &stdWriter{
        Writer: w,
        prefix: byte(t),
    }
}

// StdCopy is a modified version of io.Copy.
//
// StdCopy will demultiplex `src`, assuming that it contains two streams,
// previously multiplexed together using a StdWriter instance.
// As it reads from `src`, StdCopy will write to `dstout` and `dsterr`.
//
// StdCopy will read until it hits EOF on `src`. It will then return a nil error.
// In other words: if `err` is non nil, it indicates a real underlying error.
//
// `written` will hold the total number of bytes written to `dstout` and `dsterr`.
func StdCopy(dstout, dsterr io.Writer, src io.Reader) (written int64, err error) {
    var (
        buf       = make([]byte, startingBufLen)
        bufLen    = len(buf)
        nr, nw    int
        er, ew    error
        out       io.Writer
        frameSize int
    )

    for {
        // Make sure we have at least a full header
        for nr < stdWriterPrefixLen {
            var nr2 int
            nr2, er = src.Read(buf[nr:])
            nr += nr2
            if er == io.EOF {
                if nr < stdWriterPrefixLen {
                    return written, nil
                }
                break
            }
            if er != nil {
                return 0, er
            }
        }

        stream := StdType(buf[stdWriterFdIndex])
        // Check the first byte to know where to write
        switch stream {
        case Stdin:
            fallthrough
        case Stdout:
            // Write on stdout
            out = dstout
        case Stderr:
            // Write on stderr
            out = dsterr
        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 input header: %d", buf[stdWriterFdIndex])
        }

        // 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, er = src.Read(buf[nr:])
            nr += nr2
            if er == io.EOF {
                if nr < frameSize+stdWriterPrefixLen {
                    return written, nil
                }
                break
            }
            if er != nil {
                return 0, er
            }
        }

        // 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, ew = out.Write(buf[stdWriterPrefixLen : frameSize+stdWriterPrefixLen])
        if ew != nil {
            return 0, ew
        }

        // 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
    }
}