Based on #8984. This patch fixes behavior when image size calculation
didn't consider hardlinks.
Signed-off-by: Dmitry Vorobev <dimahabr@gmail.com>
... | ... |
@@ -249,14 +249,14 @@ func (ta *tarAppender) addTarFile(path, name string) error { |
249 | 249 |
} |
250 | 250 |
hdr.Name = name |
251 | 251 |
|
252 |
- nlink, inode, err := setHeaderForSpecialDevice(hdr, ta, name, fi.Sys()) |
|
252 |
+ inode, err := setHeaderForSpecialDevice(hdr, ta, name, fi.Sys()) |
|
253 | 253 |
if err != nil { |
254 | 254 |
return err |
255 | 255 |
} |
256 | 256 |
|
257 |
- // if it's a regular file and has more than 1 link, |
|
257 |
+ // if it's not a directory and has more than 1 link, |
|
258 | 258 |
// it's hardlinked, so set the type flag accordingly |
259 |
- if fi.Mode().IsRegular() && nlink > 1 { |
|
259 |
+ if !fi.IsDir() && hasHardlinks(fi) { |
|
260 | 260 |
// a link should have a name that it links too |
261 | 261 |
// and that linked name should be first in the tar archive |
262 | 262 |
if oldpath, ok := ta.SeenFiles[inode]; ok { |
... | ... |
@@ -40,7 +40,7 @@ func chmodTarEntry(perm os.FileMode) os.FileMode { |
40 | 40 |
return perm // noop for unix as golang APIs provide perm bits correctly |
41 | 41 |
} |
42 | 42 |
|
43 |
-func setHeaderForSpecialDevice(hdr *tar.Header, ta *tarAppender, name string, stat interface{}) (nlink uint32, inode uint64, err error) { |
|
43 |
+func setHeaderForSpecialDevice(hdr *tar.Header, ta *tarAppender, name string, stat interface{}) (inode uint64, err error) { |
|
44 | 44 |
s, ok := stat.(*syscall.Stat_t) |
45 | 45 |
|
46 | 46 |
if !ok { |
... | ... |
@@ -48,7 +48,6 @@ func setHeaderForSpecialDevice(hdr *tar.Header, ta *tarAppender, name string, st |
48 | 48 |
return |
49 | 49 |
} |
50 | 50 |
|
51 |
- nlink = uint32(s.Nlink) |
|
52 | 51 |
inode = uint64(s.Ino) |
53 | 52 |
|
54 | 53 |
// Currently go does not fill in the major/minors |
... | ... |
@@ -49,7 +49,7 @@ func chmodTarEntry(perm os.FileMode) os.FileMode { |
49 | 49 |
return perm |
50 | 50 |
} |
51 | 51 |
|
52 |
-func setHeaderForSpecialDevice(hdr *tar.Header, ta *tarAppender, name string, stat interface{}) (nlink uint32, inode uint64, err error) { |
|
52 |
+func setHeaderForSpecialDevice(hdr *tar.Header, ta *tarAppender, name string, stat interface{}) (inode uint64, err error) { |
|
53 | 53 |
// do nothing. no notion of Rdev, Inode, Nlink in stat on Windows |
54 | 54 |
return |
55 | 55 |
} |
... | ... |
@@ -328,13 +328,29 @@ func ChangesDirs(newDir, oldDir string) ([]Change, error) { |
328 | 328 |
|
329 | 329 |
// ChangesSize calculates the size in bytes of the provided changes, based on newDir. |
330 | 330 |
func ChangesSize(newDir string, changes []Change) int64 { |
331 |
- var size int64 |
|
331 |
+ var ( |
|
332 |
+ size int64 |
|
333 |
+ sf = make(map[uint64]struct{}) |
|
334 |
+ ) |
|
332 | 335 |
for _, change := range changes { |
333 | 336 |
if change.Kind == ChangeModify || change.Kind == ChangeAdd { |
334 | 337 |
file := filepath.Join(newDir, change.Path) |
335 |
- fileInfo, _ := os.Lstat(file) |
|
338 |
+ fileInfo, err := os.Lstat(file) |
|
339 |
+ if err != nil { |
|
340 |
+ logrus.Errorf("Can not stat %q: %s", file, err) |
|
341 |
+ continue |
|
342 |
+ } |
|
343 |
+ |
|
336 | 344 |
if fileInfo != nil && !fileInfo.IsDir() { |
337 |
- size += fileInfo.Size() |
|
345 |
+ if hasHardlinks(fileInfo) { |
|
346 |
+ inode := getIno(fileInfo) |
|
347 |
+ if _, ok := sf[inode]; !ok { |
|
348 |
+ size += fileInfo.Size() |
|
349 |
+ sf[inode] = struct{}{} |
|
350 |
+ } |
|
351 |
+ } else { |
|
352 |
+ size += fileInfo.Size() |
|
353 |
+ } |
|
338 | 354 |
} |
339 | 355 |
} |
340 | 356 |
} |
... | ... |
@@ -434,6 +434,35 @@ func TestApplyLayer(t *testing.T) { |
434 | 434 |
} |
435 | 435 |
} |
436 | 436 |
|
437 |
+func TestChangesSizeWithHardlinks(t *testing.T) { |
|
438 |
+ srcDir, err := ioutil.TempDir("", "docker-test-srcDir") |
|
439 |
+ if err != nil { |
|
440 |
+ t.Fatal(err) |
|
441 |
+ } |
|
442 |
+ defer os.RemoveAll(srcDir) |
|
443 |
+ |
|
444 |
+ destDir, err := ioutil.TempDir("", "docker-test-destDir") |
|
445 |
+ if err != nil { |
|
446 |
+ t.Fatal(err) |
|
447 |
+ } |
|
448 |
+ defer os.RemoveAll(destDir) |
|
449 |
+ |
|
450 |
+ creationSize, err := prepareUntarSourceDirectory(100, destDir, true) |
|
451 |
+ if err != nil { |
|
452 |
+ t.Fatal(err) |
|
453 |
+ } |
|
454 |
+ |
|
455 |
+ changes, err := ChangesDirs(destDir, srcDir) |
|
456 |
+ if err != nil { |
|
457 |
+ t.Fatal(err) |
|
458 |
+ } |
|
459 |
+ |
|
460 |
+ got := ChangesSize(destDir, changes) |
|
461 |
+ if got != int64(creationSize) { |
|
462 |
+ t.Errorf("Expected %d bytes of changes, got %d", creationSize, got) |
|
463 |
+ } |
|
464 |
+} |
|
465 |
+ |
|
437 | 466 |
func TestChangesSizeWithNoChanges(t *testing.T) { |
438 | 467 |
size := ChangesSize("/tmp", nil) |
439 | 468 |
if size != 0 { |
... | ... |
@@ -468,7 +497,7 @@ func TestChangesSize(t *testing.T) { |
468 | 468 |
} |
469 | 469 |
size := ChangesSize(parentPath, changes) |
470 | 470 |
if size != 6 { |
471 |
- t.Fatalf("ChangesSizes with only delete changes should be 0, was %d", size) |
|
471 |
+ t.Fatalf("Expected 6 bytes of changes, got %d", size) |
|
472 | 472 |
} |
473 | 473 |
} |
474 | 474 |
|
... | ... |
@@ -3,6 +3,7 @@ |
3 | 3 |
package archive |
4 | 4 |
|
5 | 5 |
import ( |
6 |
+ "os" |
|
6 | 7 |
"syscall" |
7 | 8 |
|
8 | 9 |
"github.com/docker/docker/pkg/system" |
... | ... |
@@ -25,3 +26,11 @@ func statDifferent(oldStat *system.StatT, newStat *system.StatT) bool { |
25 | 25 |
func (info *FileInfo) isDir() bool { |
26 | 26 |
return info.parent == nil || info.stat.Mode()&syscall.S_IFDIR != 0 |
27 | 27 |
} |
28 |
+ |
|
29 |
+func getIno(fi os.FileInfo) uint64 { |
|
30 |
+ return uint64(fi.Sys().(*syscall.Stat_t).Ino) |
|
31 |
+} |
|
32 |
+ |
|
33 |
+func hasHardlinks(fi os.FileInfo) bool { |
|
34 |
+ return fi.Sys().(*syscall.Stat_t).Nlink > 1 |
|
35 |
+} |
... | ... |
@@ -1,6 +1,8 @@ |
1 | 1 |
package archive |
2 | 2 |
|
3 | 3 |
import ( |
4 |
+ "os" |
|
5 |
+ |
|
4 | 6 |
"github.com/docker/docker/pkg/system" |
5 | 7 |
) |
6 | 8 |
|
... | ... |
@@ -18,3 +20,11 @@ func statDifferent(oldStat *system.StatT, newStat *system.StatT) bool { |
18 | 18 |
func (info *FileInfo) isDir() bool { |
19 | 19 |
return info.parent == nil || info.stat.IsDir() |
20 | 20 |
} |
21 |
+ |
|
22 |
+func getIno(fi os.FileInfo) (inode uint64) { |
|
23 |
+ return |
|
24 |
+} |
|
25 |
+ |
|
26 |
+func hasHardlinks(fi os.FileInfo) bool { |
|
27 |
+ return false |
|
28 |
+} |