diff --git a/pkg/archive/archive.go b/pkg/archive/archive.go index 50fbbba0b2e238349ad26c2e1c242e6d2bed9800..9e8225ebfa6c7fc0e0b6b4cbb70bd1b970929da2 100644 --- a/pkg/archive/archive.go +++ b/pkg/archive/archive.go @@ -249,14 +249,14 @@ func (ta *tarAppender) addTarFile(path, name string) error { } hdr.Name = name - nlink, inode, err := setHeaderForSpecialDevice(hdr, ta, name, fi.Sys()) + inode, err := setHeaderForSpecialDevice(hdr, ta, name, fi.Sys()) if err != nil { return err } - // if it's a regular file and has more than 1 link, + // if it's not a directory and has more than 1 link, // it's hardlinked, so set the type flag accordingly - if fi.Mode().IsRegular() && nlink > 1 { + if !fi.IsDir() && hasHardlinks(fi) { // a link should have a name that it links too // and that linked name should be first in the tar archive if oldpath, ok := ta.SeenFiles[inode]; ok { diff --git a/pkg/archive/archive_unix.go b/pkg/archive/archive_unix.go index 51372d52c32cd303e48d88069ff4fa3dc21505fa..abf9ad7802eaec0a8931c4fe390a7678881ec022 100644 --- a/pkg/archive/archive_unix.go +++ b/pkg/archive/archive_unix.go @@ -40,7 +40,7 @@ func chmodTarEntry(perm os.FileMode) os.FileMode { return perm // noop for unix as golang APIs provide perm bits correctly } -func setHeaderForSpecialDevice(hdr *tar.Header, ta *tarAppender, name string, stat interface{}) (nlink uint32, inode uint64, err error) { +func setHeaderForSpecialDevice(hdr *tar.Header, ta *tarAppender, name string, stat interface{}) (inode uint64, err error) { s, ok := stat.(*syscall.Stat_t) if !ok { @@ -48,7 +48,6 @@ func setHeaderForSpecialDevice(hdr *tar.Header, ta *tarAppender, name string, st return } - nlink = uint32(s.Nlink) inode = uint64(s.Ino) // Currently go does not fill in the major/minors diff --git a/pkg/archive/archive_windows.go b/pkg/archive/archive_windows.go index f5cc997b90ba5154cb53b76df9cca85134613e50..b348cde6a0c5ac3df037cc53ca40f685c9858c0b 100644 --- a/pkg/archive/archive_windows.go +++ b/pkg/archive/archive_windows.go @@ -49,7 +49,7 @@ func chmodTarEntry(perm os.FileMode) os.FileMode { return perm } -func setHeaderForSpecialDevice(hdr *tar.Header, ta *tarAppender, name string, stat interface{}) (nlink uint32, inode uint64, err error) { +func setHeaderForSpecialDevice(hdr *tar.Header, ta *tarAppender, name string, stat interface{}) (inode uint64, err error) { // do nothing. no notion of Rdev, Inode, Nlink in stat on Windows return } diff --git a/pkg/archive/changes.go b/pkg/archive/changes.go index e3003dbd82fcd8fb6977f04f951d7f5306e41dd6..49593eff45c8c92a49b4e47fb1e10ce27dbca469 100644 --- a/pkg/archive/changes.go +++ b/pkg/archive/changes.go @@ -328,13 +328,29 @@ func ChangesDirs(newDir, oldDir string) ([]Change, error) { // ChangesSize calculates the size in bytes of the provided changes, based on newDir. func ChangesSize(newDir string, changes []Change) int64 { - var size int64 + var ( + size int64 + sf = make(map[uint64]struct{}) + ) for _, change := range changes { if change.Kind == ChangeModify || change.Kind == ChangeAdd { file := filepath.Join(newDir, change.Path) - fileInfo, _ := os.Lstat(file) + fileInfo, err := os.Lstat(file) + if err != nil { + logrus.Errorf("Can not stat %q: %s", file, err) + continue + } + if fileInfo != nil && !fileInfo.IsDir() { - size += fileInfo.Size() + if hasHardlinks(fileInfo) { + inode := getIno(fileInfo) + if _, ok := sf[inode]; !ok { + size += fileInfo.Size() + sf[inode] = struct{}{} + } + } else { + size += fileInfo.Size() + } } } } diff --git a/pkg/archive/changes_test.go b/pkg/archive/changes_test.go index afbb0b9757bdb913f886ea47664789a005ee8427..52daaa64336b10ffd41d7e5aa3e86696608970f0 100644 --- a/pkg/archive/changes_test.go +++ b/pkg/archive/changes_test.go @@ -434,6 +434,35 @@ func TestApplyLayer(t *testing.T) { } } +func TestChangesSizeWithHardlinks(t *testing.T) { + srcDir, err := ioutil.TempDir("", "docker-test-srcDir") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(srcDir) + + destDir, err := ioutil.TempDir("", "docker-test-destDir") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(destDir) + + creationSize, err := prepareUntarSourceDirectory(100, destDir, true) + if err != nil { + t.Fatal(err) + } + + changes, err := ChangesDirs(destDir, srcDir) + if err != nil { + t.Fatal(err) + } + + got := ChangesSize(destDir, changes) + if got != int64(creationSize) { + t.Errorf("Expected %d bytes of changes, got %d", creationSize, got) + } +} + func TestChangesSizeWithNoChanges(t *testing.T) { size := ChangesSize("/tmp", nil) if size != 0 { @@ -468,7 +497,7 @@ func TestChangesSize(t *testing.T) { } size := ChangesSize(parentPath, changes) if size != 6 { - t.Fatalf("ChangesSizes with only delete changes should be 0, was %d", size) + t.Fatalf("Expected 6 bytes of changes, got %d", size) } } diff --git a/pkg/archive/changes_unix.go b/pkg/archive/changes_unix.go index 05f109afac58766000422336480ffa3fe4553b4f..3778b732cf402c28f634a493f147ca5f0d0aecb4 100644 --- a/pkg/archive/changes_unix.go +++ b/pkg/archive/changes_unix.go @@ -3,6 +3,7 @@ package archive import ( + "os" "syscall" "github.com/docker/docker/pkg/system" @@ -25,3 +26,11 @@ func statDifferent(oldStat *system.StatT, newStat *system.StatT) bool { func (info *FileInfo) isDir() bool { return info.parent == nil || info.stat.Mode()&syscall.S_IFDIR != 0 } + +func getIno(fi os.FileInfo) uint64 { + return uint64(fi.Sys().(*syscall.Stat_t).Ino) +} + +func hasHardlinks(fi os.FileInfo) bool { + return fi.Sys().(*syscall.Stat_t).Nlink > 1 +} diff --git a/pkg/archive/changes_windows.go b/pkg/archive/changes_windows.go index 135db369e52579d0f391a9af3993ec64d8c21e22..af94243fc4b43d193654f57ff5a662b70fc21229 100644 --- a/pkg/archive/changes_windows.go +++ b/pkg/archive/changes_windows.go @@ -1,6 +1,8 @@ package archive import ( + "os" + "github.com/docker/docker/pkg/system" ) @@ -18,3 +20,11 @@ func statDifferent(oldStat *system.StatT, newStat *system.StatT) bool { func (info *FileInfo) isDir() bool { return info.parent == nil || info.stat.IsDir() } + +func getIno(fi os.FileInfo) (inode uint64) { + return +} + +func hasHardlinks(fi os.FileInfo) bool { + return false +}