Implement UnTar via archive/tar

This replaces the shelling out to tar with a reimplementation of untar
based on the archive/tar code and the pre-existing code from ApplyLayer
to create real files from tar headers.

Docker-DCO-1.1-Signed-off-by: Alexander Larsson <alexl@redhat.com> (github: alexlarsson)
This commit is contained in:
Alexander Larsson 2013-12-20 12:08:34 +01:00
parent 710d5a48fb
commit a4868e233c
1 changed files with 73 additions and 25 deletions

View File

@ -13,6 +13,7 @@ import (
"os/exec" "os/exec"
"path" "path"
"path/filepath" "path/filepath"
"strings"
"syscall" "syscall"
) )
@ -285,45 +286,92 @@ func TarFilter(path string, options *TarOptions) (io.Reader, error) {
// The archive may be compressed with one of the following algorithms: // The archive may be compressed with one of the following algorithms:
// identity (uncompressed), gzip, bzip2, xz. // identity (uncompressed), gzip, bzip2, xz.
// FIXME: specify behavior when target path exists vs. doesn't exist. // FIXME: specify behavior when target path exists vs. doesn't exist.
func Untar(archive io.Reader, path string, options *TarOptions) error { func Untar(archive io.Reader, dest string, options *TarOptions) error {
if archive == nil { if archive == nil {
return fmt.Errorf("Empty archive") return fmt.Errorf("Empty archive")
} }
buf := make([]byte, 10) archive, err := DecompressStream(archive)
totalN := 0 if err != nil {
for totalN < 10 { return err
n, err := archive.Read(buf[totalN:]) }
tr := tar.NewReader(archive)
var dirs []*tar.Header
// Iterate through the files in the archive.
for {
hdr, err := tr.Next()
if err == io.EOF {
// end of tar archive
break
}
if err != nil { if err != nil {
if err == io.EOF {
return fmt.Errorf("Tarball too short")
}
return err return err
} }
totalN += n
utils.Debugf("[tar autodetect] n: %d", n)
}
compression := DetectCompression(buf) if options != nil {
excludeFile := false
for _, exclude := range options.Excludes {
if strings.HasPrefix(hdr.Name, exclude) {
excludeFile = true
break
}
}
if excludeFile {
continue
}
}
utils.Debugf("Archive compression detected: %s", compression.Extension()) // Normalize name, for safety and for a simple is-root check
args := []string{"--numeric-owner", "-f", "-", "-C", path, "-x" + compression.Flag()} hdr.Name = filepath.Clean(hdr.Name)
if options != nil { if !strings.HasSuffix(hdr.Name, "/") {
for _, exclude := range options.Excludes { // Not the root directory, ensure that the parent directory exists
args = append(args, fmt.Sprintf("--exclude=%s", exclude)) parent := filepath.Dir(hdr.Name)
parentPath := filepath.Join(dest, parent)
if _, err := os.Lstat(parentPath); err != nil && os.IsNotExist(err) {
err = os.MkdirAll(parentPath, 600)
if err != nil {
return err
}
}
}
path := filepath.Join(dest, hdr.Name)
// If path exits we almost always just want to remove and replace it
// The only exception is when it is a directory *and* the file from
// the layer is also a directory. Then we want to merge them (i.e.
// just apply the metadata from the layer).
if fi, err := os.Lstat(path); err == nil {
if !(fi.IsDir() && hdr.Typeflag == tar.TypeDir) {
if err := os.RemoveAll(path); err != nil {
return err
}
}
}
if err := createTarFile(path, dest, hdr, tr); err != nil {
return err
}
// Directory mtimes must be handled at the end to avoid further
// file creation in them to modify the directory mtime
if hdr.Typeflag == tar.TypeDir {
dirs = append(dirs, hdr)
} }
} }
cmd := exec.Command("tar", args...) for _, hdr := range dirs {
cmd.Stdin = io.MultiReader(bytes.NewReader(buf), archive) path := filepath.Join(dest, hdr.Name)
// Hardcode locale environment for predictable outcome regardless of host configuration. ts := []syscall.Timespec{timeToTimespec(hdr.AccessTime), timeToTimespec(hdr.ModTime)}
// (see https://github.com/dotcloud/docker/issues/355) if err := syscall.UtimesNano(path, ts); err != nil {
cmd.Env = []string{"LANG=en_US.utf-8", "LC_ALL=en_US.utf-8"} return err
output, err := cmd.CombinedOutput() }
if err != nil {
return fmt.Errorf("%s: %s", err, output)
} }
return nil return nil
} }