| package archive |
| |
| import ( |
| "fmt" |
| "io" |
| "io/ioutil" |
| "os" |
| "path/filepath" |
| "strings" |
| "syscall" |
| |
| "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" |
| |
| "github.com/docker/docker/pkg/pools" |
| ) |
| |
| // 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)) |
| } |
| |
| // 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) |
| trBuf := pools.BufioReader32KPool.Get(tr) |
| defer pools.BufioReader32KPool.Put(trBuf) |
| |
| 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, 0600) |
| 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, true); 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 |
| } |
| } |
| } |
| |
| trBuf.Reset(tr) |
| srcData := io.Reader(trBuf) |
| 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, true); 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 |
| } |