fs/diff: support symlink to abspath with second-precision mtime
Previously, `Changes()` for a symlink to absolute path, with second-precision mtime of the link itself always resulted in ENOENT. This is because `compareFileContent()` was called for the link target path, without changing the root path. Fix moby/buildkit#172 Signed-off-by: Akihiro Suda <suda.akihiro@lab.ntt.co.jp>
This commit is contained in:
parent
1bd39d36ed
commit
a3aeb398fe
@ -150,27 +150,27 @@ func TestUpdateWithSameTime(t *testing.T) {
|
|||||||
t2 := tt.Add(6 * time.Nanosecond)
|
t2 := tt.Add(6 * time.Nanosecond)
|
||||||
l1 := fstest.Apply(
|
l1 := fstest.Apply(
|
||||||
fstest.CreateFile("/file-modified-time", []byte("1"), 0644),
|
fstest.CreateFile("/file-modified-time", []byte("1"), 0644),
|
||||||
fstest.Chtime("/file-modified-time", t1),
|
fstest.Chtimes("/file-modified-time", t1, t1),
|
||||||
fstest.CreateFile("/file-no-change", []byte("1"), 0644),
|
fstest.CreateFile("/file-no-change", []byte("1"), 0644),
|
||||||
fstest.Chtime("/file-no-change", t1),
|
fstest.Chtimes("/file-no-change", t1, t1),
|
||||||
fstest.CreateFile("/file-same-time", []byte("1"), 0644),
|
fstest.CreateFile("/file-same-time", []byte("1"), 0644),
|
||||||
fstest.Chtime("/file-same-time", t1),
|
fstest.Chtimes("/file-same-time", t1, t1),
|
||||||
fstest.CreateFile("/file-truncated-time-1", []byte("1"), 0644),
|
fstest.CreateFile("/file-truncated-time-1", []byte("1"), 0644),
|
||||||
fstest.Chtime("/file-truncated-time-1", t1),
|
fstest.Chtimes("/file-truncated-time-1", t1, t1),
|
||||||
fstest.CreateFile("/file-truncated-time-2", []byte("1"), 0644),
|
fstest.CreateFile("/file-truncated-time-2", []byte("1"), 0644),
|
||||||
fstest.Chtime("/file-truncated-time-2", tt),
|
fstest.Chtimes("/file-truncated-time-2", tt, tt),
|
||||||
)
|
)
|
||||||
l2 := fstest.Apply(
|
l2 := fstest.Apply(
|
||||||
fstest.CreateFile("/file-modified-time", []byte("2"), 0644),
|
fstest.CreateFile("/file-modified-time", []byte("2"), 0644),
|
||||||
fstest.Chtime("/file-modified-time", t2),
|
fstest.Chtimes("/file-modified-time", t2, t2),
|
||||||
fstest.CreateFile("/file-no-change", []byte("1"), 0644),
|
fstest.CreateFile("/file-no-change", []byte("1"), 0644),
|
||||||
fstest.Chtime("/file-no-change", tt), // use truncated time, should be regarded as no change
|
fstest.Chtimes("/file-no-change", tt, tt), // use truncated time, should be regarded as no change
|
||||||
fstest.CreateFile("/file-same-time", []byte("2"), 0644),
|
fstest.CreateFile("/file-same-time", []byte("2"), 0644),
|
||||||
fstest.Chtime("/file-same-time", t1),
|
fstest.Chtimes("/file-same-time", t1, t1),
|
||||||
fstest.CreateFile("/file-truncated-time-1", []byte("2"), 0644),
|
fstest.CreateFile("/file-truncated-time-1", []byte("2"), 0644),
|
||||||
fstest.Chtime("/file-truncated-time-1", tt),
|
fstest.Chtimes("/file-truncated-time-1", tt, tt),
|
||||||
fstest.CreateFile("/file-truncated-time-2", []byte("2"), 0644),
|
fstest.CreateFile("/file-truncated-time-2", []byte("2"), 0644),
|
||||||
fstest.Chtime("/file-truncated-time-2", tt),
|
fstest.Chtimes("/file-truncated-time-2", tt, tt),
|
||||||
)
|
)
|
||||||
diff := []TestChange{
|
diff := []TestChange{
|
||||||
// "/file-same-time" excluded because matching non-zero nanosecond values
|
// "/file-same-time" excluded because matching non-zero nanosecond values
|
||||||
@ -184,6 +184,28 @@ func TestUpdateWithSameTime(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// buildkit#172
|
||||||
|
func TestLchtimes(t *testing.T) {
|
||||||
|
skipDiffTestOnWindows(t)
|
||||||
|
mtimes := []time.Time{
|
||||||
|
time.Unix(0, 0), // nsec is 0
|
||||||
|
time.Unix(0, 42), // nsec > 0
|
||||||
|
}
|
||||||
|
for _, mtime := range mtimes {
|
||||||
|
atime := time.Unix(424242, 42)
|
||||||
|
l1 := fstest.Apply(
|
||||||
|
fstest.CreateFile("/foo", []byte("foo"), 0644),
|
||||||
|
fstest.Symlink("/foo", "/lnk0"),
|
||||||
|
fstest.Lchtimes("/lnk0", atime, mtime),
|
||||||
|
)
|
||||||
|
l2 := fstest.Apply() // empty
|
||||||
|
diff := []TestChange{}
|
||||||
|
if err := testDiffWithBase(l1, l2, diff); err != nil {
|
||||||
|
t.Fatalf("Failed diff with base: %+v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func testDiffWithBase(base, diff fstest.Applier, expected []TestChange) error {
|
func testDiffWithBase(base, diff fstest.Applier, expected []TestChange) error {
|
||||||
t1, err := ioutil.TempDir("", "diff-with-base-lower-")
|
t1, err := ioutil.TempDir("", "diff-with-base-lower-")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -71,10 +71,11 @@ func Chown(name string, uid, gid int) Applier {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Chtime changes access and mod time of file
|
// Chtimes changes access and mod time of file.
|
||||||
func Chtime(name string, t time.Time) Applier {
|
// Use Lchtimes for symbolic links.
|
||||||
|
func Chtimes(name string, atime, mtime time.Time) Applier {
|
||||||
return applyFn(func(root string) error {
|
return applyFn(func(root string) error {
|
||||||
return os.Chtimes(filepath.Join(root, name), t, t)
|
return os.Chtimes(filepath.Join(root, name), atime, mtime)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,7 +2,13 @@
|
|||||||
|
|
||||||
package fstest
|
package fstest
|
||||||
|
|
||||||
import "github.com/containerd/continuity/sysx"
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/containerd/continuity/sysx"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
// SetXAttr sets the xatter for the file
|
// SetXAttr sets the xatter for the file
|
||||||
func SetXAttr(name, key, value string) Applier {
|
func SetXAttr(name, key, value string) Applier {
|
||||||
@ -10,3 +16,14 @@ func SetXAttr(name, key, value string) Applier {
|
|||||||
return sysx.LSetxattr(name, key, []byte(value), 0)
|
return sysx.LSetxattr(name, key, []byte(value), 0)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Lchtimes changes access and mod time of file without following symlink
|
||||||
|
func Lchtimes(name string, atime, mtime time.Time) Applier {
|
||||||
|
return applyFn(func(root string) error {
|
||||||
|
path := filepath.Join(root, name)
|
||||||
|
at := unix.NsecToTimespec(atime.UnixNano())
|
||||||
|
mt := unix.NsecToTimespec(mtime.UnixNano())
|
||||||
|
utimes := [2]unix.Timespec{at, mt}
|
||||||
|
return unix.UtimesNanoAt(unix.AT_FDCWD, path, utimes[0:], unix.AT_SYMLINK_NOFOLLOW)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
14
fs/fstest/file_windows.go
Normal file
14
fs/fstest/file_windows.go
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
package fstest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/containerd/containerd/errdefs"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Lchtimes changes access and mod time of file without following symlink
|
||||||
|
func Lchtimes(name string, atime, mtime time.Time) Applier {
|
||||||
|
return applyFn(func(root string) error {
|
||||||
|
return errdefs.ErrNotImplemented
|
||||||
|
})
|
||||||
|
}
|
25
fs/path.go
25
fs/path.go
@ -74,11 +74,14 @@ func sameFile(f1, f2 *currentPath) (bool, error) {
|
|||||||
// If the timestamp may have been truncated in one of the
|
// If the timestamp may have been truncated in one of the
|
||||||
// files, check content of file to determine difference
|
// files, check content of file to determine difference
|
||||||
if t1.Nanosecond() == 0 || t2.Nanosecond() == 0 {
|
if t1.Nanosecond() == 0 || t2.Nanosecond() == 0 {
|
||||||
if f1.f.Size() > 0 {
|
var eq bool
|
||||||
eq, err := compareFileContent(f1.fullPath, f2.fullPath)
|
if (f1.f.Mode() & os.ModeSymlink) == os.ModeSymlink {
|
||||||
if err != nil || !eq {
|
eq, err = compareSymlinkTarget(f1.fullPath, f2.fullPath)
|
||||||
return eq, err
|
} else if f1.f.Size() > 0 {
|
||||||
}
|
eq, err = compareFileContent(f1.fullPath, f2.fullPath)
|
||||||
|
}
|
||||||
|
if err != nil || !eq {
|
||||||
|
return eq, err
|
||||||
}
|
}
|
||||||
} else if t1.Nanosecond() != t2.Nanosecond() {
|
} else if t1.Nanosecond() != t2.Nanosecond() {
|
||||||
return false, nil
|
return false, nil
|
||||||
@ -88,6 +91,18 @@ func sameFile(f1, f2 *currentPath) (bool, error) {
|
|||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func compareSymlinkTarget(p1, p2 string) (bool, error) {
|
||||||
|
t1, err := os.Readlink(p1)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
t2, err := os.Readlink(p2)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return t1 == t2, nil
|
||||||
|
}
|
||||||
|
|
||||||
const compareChuckSize = 32 * 1024
|
const compareChuckSize = 32 * 1024
|
||||||
|
|
||||||
// compareFileContent compares the content of 2 same sized files
|
// compareFileContent compares the content of 2 same sized files
|
||||||
|
Loading…
Reference in New Issue
Block a user