dotcloud/docker

View on GitHub
pkg/chrootarchive/archive_unix_nolinux.go

Summary

Maintainability
A
1 hr
Test Coverage
//go:build unix && !linux

package chrootarchive // import "github.com/docker/docker/pkg/chrootarchive"

import (
    "bytes"
    "encoding/json"
    "fmt"
    "io"
    "os"
    "syscall"

    "github.com/docker/docker/pkg/archive"
    "github.com/docker/docker/pkg/reexec"
    "github.com/pkg/errors"
    "golang.org/x/sys/unix"
)

const (
    packCmd        = "chrootarchive-pack-in-chroot"
    unpackCmd      = "chrootarchive-unpack-in-chroot"
    unpackLayerCmd = "chrootarchive-unpack-layer-in-chroot"
)

func init() {
    reexec.Register(packCmd, reexecMain(packInChroot))
    reexec.Register(unpackCmd, reexecMain(unpackInChroot))
    reexec.Register(unpackLayerCmd, reexecMain(unpackLayerInChroot))
}

func reexecMain(f func(options archive.TarOptions, args ...string) error) func() {
    return func() {
        if len(os.Args) < 2 {
            fmt.Fprintln(os.Stderr, "root parameter is required")
            os.Exit(1)
        }

        options, err := recvOptions()
        root := os.Args[1]

        if err != nil {
            fmt.Fprintln(os.Stderr, err)
            os.Exit(1)
        }

        if err := syscall.Chroot(root); err != nil {
            fmt.Fprintln(
                os.Stderr,
                os.PathError{Op: "chroot", Path: root, Err: err},
            )
            os.Exit(2)
        }

        if err := f(*options, os.Args[2:]...); err != nil {
            fmt.Fprintln(os.Stderr, err)
            os.Exit(3)
        }
    }
}

func doUnpack(decompressedArchive io.Reader, relDest, root string, options *archive.TarOptions) error {
    optionsR, optionsW, err := os.Pipe()
    if err != nil {
        return err
    }
    defer optionsW.Close()
    defer optionsR.Close()

    stderr := bytes.NewBuffer(nil)

    cmd := reexec.Command(unpackCmd, root, relDest)
    cmd.Stdin = decompressedArchive
    cmd.Stderr = stderr
    cmd.ExtraFiles = []*os.File{
        optionsR,
    }

    if err = cmd.Start(); err != nil {
        return errors.Wrap(err, "re-exec error")
    }

    if err = json.NewEncoder(optionsW).Encode(options); err != nil {
        return errors.Wrap(err, "tar options encoding failed")
    }

    if err = cmd.Wait(); err != nil {
        return errors.Wrap(err, stderr.String())
    }

    return nil
}

func doPack(relSrc, root string, options *archive.TarOptions) (io.ReadCloser, error) {
    optionsR, optionsW, err := os.Pipe()
    if err != nil {
        return nil, err
    }
    defer optionsW.Close()
    defer optionsR.Close()

    stderr := bytes.NewBuffer(nil)
    cmd := reexec.Command(packCmd, root, relSrc)
    cmd.ExtraFiles = []*os.File{
        optionsR,
    }
    cmd.Stderr = stderr
    stdout, err := cmd.StdoutPipe()
    if err != nil {
        return nil, err
    }

    r, w := io.Pipe()

    if err = cmd.Start(); err != nil {
        return nil, errors.Wrap(err, "re-exec error")
    }

    go func() {
        _, _ = io.Copy(w, stdout)
        // Cleanup once stdout pipe is closed.
        if err = cmd.Wait(); err != nil {
            r.CloseWithError(errors.Wrap(err, stderr.String()))
        } else {
            r.Close()
        }
    }()

    if err = json.NewEncoder(optionsW).Encode(options); err != nil {
        return nil, errors.Wrap(err, "tar options encoding failed")
    }

    return r, nil
}

func doUnpackLayer(root string, layer io.Reader, options *archive.TarOptions) (int64, error) {
    var result int64
    optionsR, optionsW, err := os.Pipe()
    if err != nil {
        return 0, err
    }
    defer optionsW.Close()
    defer optionsR.Close()
    buffer := bytes.NewBuffer(nil)

    cmd := reexec.Command(unpackLayerCmd, root)
    cmd.Stdin = layer
    cmd.Stdout = buffer
    cmd.Stderr = buffer
    cmd.ExtraFiles = []*os.File{
        optionsR,
    }

    if err = cmd.Start(); err != nil {
        return 0, errors.Wrap(err, "re-exec error")
    }

    if err = json.NewEncoder(optionsW).Encode(options); err != nil {
        return 0, errors.Wrap(err, "tar options encoding failed")
    }

    if err = cmd.Wait(); err != nil {
        return 0, errors.Wrap(err, buffer.String())
    }

    if err = json.NewDecoder(buffer).Decode(&result); err != nil {
        return 0, errors.Wrap(err, "json decoding error")
    }

    return result, nil
}

func unpackInChroot(options archive.TarOptions, args ...string) error {
    if len(args) < 1 {
        return fmt.Errorf("destination parameter is required")
    }

    relDest := args[0]

    return archive.Unpack(os.Stdin, relDest, &options)
}

func packInChroot(options archive.TarOptions, args ...string) error {
    if len(args) < 1 {
        return fmt.Errorf("source parameter is required")
    }

    relSrc := args[0]

    tb, err := archive.NewTarballer(relSrc, &options)
    if err != nil {
        return err
    }

    go tb.Do()

    _, err = io.Copy(os.Stdout, tb.Reader())

    return err
}

func unpackLayerInChroot(options archive.TarOptions, _args ...string) error {
    // We need to be able to set any perms
    _ = unix.Umask(0)

    size, err := archive.UnpackLayer("/", os.Stdin, &options)
    if err != nil {
        return err
    }

    return json.NewEncoder(os.Stdout).Encode(size)
}

func recvOptions() (*archive.TarOptions, error) {
    var options archive.TarOptions
    optionsPipe := os.NewFile(3, "tar-options")
    if optionsPipe == nil {
        return nil, fmt.Errorf("could not read tar options from the pipe")
    }
    defer optionsPipe.Close()
    err := json.NewDecoder(optionsPipe).Decode(&options)
    if err != nil {
        return &options, err
    }

    return &options, nil
}