mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
6889cd9f9c
Files in the .wh..wh.plnk directory are ignored, but other files inside the tarfile can be hardlinks to these files. This is not something that normally happens, as on aufs unmount such files are supposed to be dropped via the "auplink" too, yet images on the index (such as shipyard/shipyard, e.g. layer f73c835af6d58b6fc827b400569f79a8f28e54f5bb732be063e1aacefbc374d0) contains such files. We handle these by extracting these files to a temporary directory and resolve such hardlinks via the temporary files. This fixes https://github.com/dotcloud/docker/issues/3884 Docker-DCO-1.1-Signed-off-by: Alexander Larsson <alexl@redhat.com> (github: alexlarsson)
160 lines
4.5 KiB
Go
160 lines
4.5 KiB
Go
package archive
|
|
|
|
import (
|
|
"code.google.com/p/go/src/pkg/archive/tar"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"syscall"
|
|
"time"
|
|
)
|
|
|
|
// Linux device nodes are a bit weird due to backwards compat with 16 bit device nodes.
|
|
// They are, from low to high: the lower 8 bits of the minor, then 12 bits of the major,
|
|
// then the top 12 bits of the minor
|
|
func mkdev(major int64, minor int64) uint32 {
|
|
return uint32(((minor & 0xfff00) << 12) | ((major & 0xfff) << 8) | (minor & 0xff))
|
|
}
|
|
func timeToTimespec(time time.Time) (ts syscall.Timespec) {
|
|
if time.IsZero() {
|
|
// Return UTIME_OMIT special value
|
|
ts.Sec = 0
|
|
ts.Nsec = ((1 << 30) - 2)
|
|
return
|
|
}
|
|
return syscall.NsecToTimespec(time.UnixNano())
|
|
}
|
|
|
|
// ApplyLayer parses a diff in the standard layer format from `layer`, and
|
|
// applies it to the directory `dest`.
|
|
func ApplyLayer(dest string, layer ArchiveReader) error {
|
|
// We need to be able to set any perms
|
|
oldmask := syscall.Umask(0)
|
|
defer syscall.Umask(oldmask)
|
|
|
|
layer, err := DecompressStream(layer)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
tr := tar.NewReader(layer)
|
|
|
|
var dirs []*tar.Header
|
|
|
|
aufsTempdir := ""
|
|
aufsHardlinks := make(map[string]*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 {
|
|
return err
|
|
}
|
|
|
|
// Normalize name, for safety and for a simple is-root check
|
|
hdr.Name = filepath.Clean(hdr.Name)
|
|
|
|
if !strings.HasSuffix(hdr.Name, "/") {
|
|
// Not the root directory, ensure that the parent directory exists.
|
|
// This happened in some tests where an image had a tarfile without any
|
|
// parent directories.
|
|
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
|
|
}
|
|
}
|
|
}
|
|
|
|
// Skip AUFS metadata dirs
|
|
if strings.HasPrefix(hdr.Name, ".wh..wh.") {
|
|
// Regular files inside /.wh..wh.plnk can be used as hardlink targets
|
|
// We don't want this directory, but we need the files in them so that
|
|
// such hardlinks can be resolved.
|
|
if strings.HasPrefix(hdr.Name, ".wh..wh.plnk") && hdr.Typeflag == tar.TypeReg {
|
|
basename := filepath.Base(hdr.Name)
|
|
aufsHardlinks[basename] = hdr
|
|
if aufsTempdir == "" {
|
|
if aufsTempdir, err = ioutil.TempDir("", "dockerplnk"); err != nil {
|
|
return err
|
|
}
|
|
defer os.RemoveAll(aufsTempdir)
|
|
}
|
|
if err := createTarFile(filepath.Join(aufsTempdir, basename), dest, hdr, tr); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
continue
|
|
}
|
|
|
|
path := filepath.Join(dest, hdr.Name)
|
|
base := filepath.Base(path)
|
|
if strings.HasPrefix(base, ".wh.") {
|
|
originalBase := base[len(".wh."):]
|
|
originalPath := filepath.Join(filepath.Dir(path), originalBase)
|
|
if err := os.RemoveAll(originalPath); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
// 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
|
|
}
|
|
}
|
|
}
|
|
|
|
srcData := io.Reader(tr)
|
|
srcHdr := hdr
|
|
|
|
// Hard links into /.wh..wh.plnk don't work, as we don't extract that directory, so
|
|
// we manually retarget these into the temporary files we extracted them into
|
|
if hdr.Typeflag == tar.TypeLink && strings.HasPrefix(filepath.Clean(hdr.Linkname), ".wh..wh.plnk") {
|
|
linkBasename := filepath.Base(hdr.Linkname)
|
|
srcHdr = aufsHardlinks[linkBasename]
|
|
if srcHdr == nil {
|
|
return fmt.Errorf("Invalid aufs hardlink")
|
|
}
|
|
tmpFile, err := os.Open(filepath.Join(aufsTempdir, linkBasename))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer tmpFile.Close()
|
|
srcData = tmpFile
|
|
}
|
|
|
|
if err := createTarFile(path, dest, srcHdr, srcData); 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)
|
|
}
|
|
}
|
|
}
|
|
|
|
for _, hdr := range dirs {
|
|
path := filepath.Join(dest, hdr.Name)
|
|
ts := []syscall.Timespec{timeToTimespec(hdr.AccessTime), timeToTimespec(hdr.ModTime)}
|
|
if err := syscall.UtimesNano(path, ts); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|