archive.go
ef711962
 package docker
97a82094
 
 import (
 	"errors"
54db1862
 	"fmt"
97a82094
 	"io"
 	"io/ioutil"
baacae83
 	"os"
97a82094
 	"os/exec"
 )
 
33f6a0aa
 type Archive io.Reader
 
97a82094
 type Compression uint32
 
 const (
 	Uncompressed Compression = iota
 	Bzip2
 	Gzip
3c5d2e46
 	Xz
97a82094
 )
 
 func (compression *Compression) Flag() string {
 	switch *compression {
 	case Bzip2:
 		return "j"
 	case Gzip:
 		return "z"
3c5d2e46
 	case Xz:
 		return "J"
97a82094
 	}
 	return ""
 }
 
54db1862
 func (compression *Compression) Extension() string {
 	switch *compression {
 	case Uncompressed:
 		return "tar"
 	case Bzip2:
 		return "tar.bz2"
 	case Gzip:
 		return "tar.gz"
 	case Xz:
 		return "tar.xz"
 	}
 	return ""
 }
 
97a82094
 func Tar(path string, compression Compression) (io.Reader, error) {
 	cmd := exec.Command("bsdtar", "-f", "-", "-C", path, "-c"+compression.Flag(), ".")
 	return CmdStream(cmd)
 }
 
5b828761
 // FIXME: specify behavior when target path exists vs. doesn't exist.
97a82094
 func Untar(archive io.Reader, path string) error {
 	cmd := exec.Command("bsdtar", "-f", "-", "-C", path, "-x")
 	cmd.Stdin = archive
561ceac5
 	// Hardcode locale environment for predictable outcome regardless of host configuration.
 	//   (see https://github.com/dotcloud/docker/issues/355)
 	cmd.Env = []string{"LANG=en_US.utf-8", "LC_ALL=en_US.utf-8"}
97a82094
 	output, err := cmd.CombinedOutput()
 	if err != nil {
54db1862
 		return fmt.Errorf("%s: %s", err, output)
97a82094
 	}
 	return nil
 }
 
5b828761
 // UntarPath is a convenience function which looks for an archive
 // at filesystem path `src`, and unpacks it at `dst`.
 func UntarPath(src, dst string) error {
 	if archive, err := os.Open(src); err != nil {
 		return err
 	} else if err := Untar(archive, dst); err != nil {
 		return err
 	}
 	return nil
 }
 
 // CopyWithTar creates a tar archive of filesystem path `src`, and
 // unpacks it at filesystem path `dst`.
 // The archive is streamed directly with fixed buffering and no
 // intermediary disk IO.
 //
 func CopyWithTar(src, dst string) error {
 	archive, err := Tar(src, Uncompressed)
 	if err != nil {
 		return err
 	}
 	return Untar(archive, dst)
 }
 
f85e6548
 // CmdStream executes a command, and returns its stdout as a stream.
 // If the command fails to run or doesn't complete successfully, an error
 // will be returned, including anything written on stderr.
97a82094
 func CmdStream(cmd *exec.Cmd) (io.Reader, error) {
 	stdout, err := cmd.StdoutPipe()
 	if err != nil {
 		return nil, err
 	}
 	stderr, err := cmd.StderrPipe()
 	if err != nil {
 		return nil, err
 	}
 	pipeR, pipeW := io.Pipe()
58befe30
 	errChan := make(chan []byte)
6ede6bc8
 	// Collect stderr, we will use it in case of an error
97a82094
 	go func() {
 		errText, e := ioutil.ReadAll(stderr)
 		if e != nil {
 			errText = []byte("(...couldn't fetch stderr: " + e.Error() + ")")
 		}
58befe30
 		errChan <- errText
 	}()
6ede6bc8
 	// Copy stdout to the returned pipe
58befe30
 	go func() {
 		_, err := io.Copy(pipeW, stdout)
 		if err != nil {
 			pipeW.CloseWithError(err)
 		}
 		errText := <-errChan
97a82094
 		if err := cmd.Wait(); err != nil {
 			pipeW.CloseWithError(errors.New(err.Error() + ": " + string(errText)))
 		} else {
 			pipeW.Close()
 		}
 	}()
6ede6bc8
 	// Run the command and return the pipe
97a82094
 	if err := cmd.Start(); err != nil {
 		return nil, err
 	}
 	return pipeR, nil
 }
baacae83
 
 // NewTempArchive reads the content of src into a temporary file, and returns the contents
 // of that file as an archive. The archive can only be read once - as soon as reading completes,
 // the file will be deleted.
 func NewTempArchive(src Archive, dir string) (*TempArchive, error) {
 	f, err := ioutil.TempFile(dir, "")
 	if err != nil {
 		return nil, err
 	}
 	if _, err := io.Copy(f, src); err != nil {
 		return nil, err
 	}
 	if _, err := f.Seek(0, 0); err != nil {
 		return nil, err
 	}
 	st, err := f.Stat()
 	if err != nil {
 		return nil, err
 	}
 	size := st.Size()
 	return &TempArchive{f, size}, nil
 }
 
 type TempArchive struct {
 	*os.File
 	Size int64 // Pre-computed from Stat().Size() as a convenience
 }
 
 func (archive *TempArchive) Read(data []byte) (int, error) {
 	n, err := archive.File.Read(data)
 	if err != nil {
 		os.Remove(archive.File.Name())
 	}
 	return n, err
 }