Browse code

Fix change detection when applying tar layers

The default gnu tar format has no sub-second precision mtime support,
and the golang tar writer currently doesn't support that either.
This means if we export the changes from a container we will not
get zeron in the sub-second precision field when the change is applied.

This means we can't compare that to the original without getting a
spurious change. So, we detect this case by treating a case where the
seconds match and either of the two nanoseconds are zero as equal.

Alexander Larsson authored on 2013/12/13 23:46:41
Showing 1 changed files
... ...
@@ -6,6 +6,7 @@ import (
6 6
 	"path/filepath"
7 7
 	"strings"
8 8
 	"syscall"
9
+	"time"
9 10
 )
10 11
 
11 12
 type ChangeType int
... ...
@@ -34,6 +35,21 @@ func (change *Change) String() string {
34 34
 	return fmt.Sprintf("%s %s", kind, change.Path)
35 35
 }
36 36
 
37
+// Gnu tar and the go tar writer don't have sub-second mtime
38
+// precision, which is problematic when we apply changes via tar
39
+// files, we handle this by comparing for exact times, *or* same
40
+// second count and either a or b having exactly 0 nanoseconds
41
+func sameFsTime(a, b time.Time) bool {
42
+	return a == b ||
43
+		(a.Unix() == b.Unix() &&
44
+			(a.Nanosecond() == 0 || b.Nanosecond() == 0))
45
+}
46
+
47
+func sameFsTimeSpec(a, b syscall.Timespec) bool {
48
+	return a.Sec == b.Sec &&
49
+		(a.Nsec == b.Nsec || a.Nsec == 0 || b.Nsec == 0)
50
+}
51
+
37 52
 func Changes(layers []string, rw string) ([]Change, error) {
38 53
 	var changes []Change
39 54
 	err := filepath.Walk(rw, func(path string, f os.FileInfo, err error) error {
... ...
@@ -85,7 +101,7 @@ func Changes(layers []string, rw string) ([]Change, error) {
85 85
 					// However, if it's a directory, maybe it wasn't actually modified.
86 86
 					// If you modify /foo/bar/baz, then /foo will be part of the changed files only because it's the parent of bar
87 87
 					if stat.IsDir() && f.IsDir() {
88
-						if f.Size() == stat.Size() && f.Mode() == stat.Mode() && f.ModTime() == stat.ModTime() {
88
+						if f.Size() == stat.Size() && f.Mode() == stat.Mode() && sameFsTime(f.ModTime(), stat.ModTime()) {
89 89
 							// Both directories are the same, don't record the change
90 90
 							return nil
91 91
 						}
... ...
@@ -181,7 +197,7 @@ func (info *FileInfo) addChanges(oldInfo *FileInfo, changes *[]Change) {
181 181
 				oldStat.Rdev != newStat.Rdev ||
182 182
 				// Don't look at size for dirs, its not a good measure of change
183 183
 				(oldStat.Size != newStat.Size && oldStat.Mode&syscall.S_IFDIR != syscall.S_IFDIR) ||
184
-				getLastModification(oldStat) != getLastModification(newStat) {
184
+				!sameFsTimeSpec(getLastModification(oldStat), getLastModification(newStat)) {
185 185
 				change := Change{
186 186
 					Path: newChild.path(),
187 187
 					Kind: ChangeModify,