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.
| ... | ... |
@@ -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, |